cpw.nader.asynclogging: 8caaf0ca: Brought over local changes into a new br...

morshed.nader at gmail.com morshed.nader at gmail.com
Mon Jan 10 12:45:27 EST 2011


----------------------------------------------------------------------
Revision: 8caaf0ca0385ed083a5f8e05073a89225efadeb1
Parent:   538b851ae7c6466621d0c3caa2e13a8a9097cf02
Author:   morshed.nader at gmail.com
Date:     01/10/11 01:31:21
Branch:   im.pidgin.cpw.nader.asynclogging
URL: http://d.pidgin.im/viewmtn/revision/info/8caaf0ca0385ed083a5f8e05073a89225efadeb1

Changelog: 

Brought over local changes into a new branch, clearing up all the messy merge absurdity on the old branch

Changes against parent 538b851ae7c6466621d0c3caa2e13a8a9097cf02

  patched  ChangeLog
  patched  ChangeLog.API
  patched  configure.ac
  patched  finch/gntlog.c
  patched  finch/gntlog.h
  patched  finch/plugins/gnthistory.c
  patched  libpurple/Makefile.mingw
  patched  libpurple/connection.c
  patched  libpurple/conversation.c
  patched  libpurple/log.c
  patched  libpurple/log.h
  patched  libpurple/status.c
  patched  pidgin/Makefile.mingw
  patched  pidgin/gtklog.c
  patched  pidgin/gtklog.h
  patched  pidgin/gtkutils.c
  patched  pidgin/plugins/Makefile.mingw
  patched  pidgin/plugins/history.c

-------------- next part --------------
============================================================
--- ChangeLog	da38619317d5f22efd76a0e65d96f4d4c1e8c442
+++ ChangeLog	089643e3d2629a73b99338f26884e09c42c4dc54
@@ -1,5 +1,19 @@ Pidgin and Finch: The Pimpin' Penguin IM
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.8.0 (??/??/????):
+	libpurple:
+	* Added a non-blocking API (#2280)
+	* Added a dependency for gio-2.0, which is packaged with glib-2.0 >= 2.16
+
+	Finch:
+	* Updated various methods to make use of the non-blocking API (#2280)
+
+	Pidgin:
+	* Updated various methods to make use of the non-blocking API (#2280)
+
+	Plugins:
+	* Updated the History and GntHistory plugins to make use of the non-blocking API (#2280)
+
 version 2.7.10 (??/??/????):
 	General:
 	* Force video sources to all have the same capabilities.  This reduces the
============================================================
--- libpurple/conversation.c	ee99c716bc1eee1b506e868d1ba00cbaf5ff511a
+++ libpurple/conversation.c	5189741e8d77163db36db82793df9a1f5a37693b
@@ -40,6 +40,8 @@ static PurpleConversationUiOps *default_
 static GList *chats = NULL;
 static PurpleConversationUiOps *default_ops = NULL;
 
+static GCancellable *write_conv_cancel = NULL;
+
 /**
  * A hash table used for efficient lookups of conversations by name.
  * struct _purple_hconv => PurpleConversation*
@@ -992,11 +994,9 @@ purple_conversation_write(PurpleConversa
 		if (conv->logs == NULL)
 			open_log(conv);
 
-		log = conv->logs;
-		while (log != NULL) {
-			purple_log_write((PurpleLog *)log->data, flags, alias, mtime, displayed);
-			log = log->next;
-		}
+		for (log = conv->logs; log != NULL; log = g_list_next(log))
+			purple_log_write_async((PurpleLog *) log->data, flags, alias, mtime, displayed,
+				G_PRIORITY_DEFAULT, write_conv_cancel, NULL, NULL);
 	}
 
 	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) {
@@ -1010,6 +1010,7 @@ purple_conversation_write(PurpleConversa
 
 	add_message_to_history(conv, who, alias, message, flags, mtime);
 
+	// We should have a "wrote-log-msg" signal!
 	purple_signal_emit(purple_conversations_get_handle(),
 		(type == PURPLE_CONV_TYPE_IM ? "wrote-im-msg" : "wrote-chat-msg"),
 		account, who, displayed, conv, flags);
@@ -2254,6 +2255,8 @@ purple_conversations_init(void)
 {
 	void *handle = purple_conversations_get_handle();
 
+	write_conv_cancel = g_cancellable_new();
+
 	conversation_cache = g_hash_table_new_full((GHashFunc)_purple_conversations_hconv_hash,
 						(GEqualFunc)_purple_conversations_hconv_equal,
 						(GDestroyNotify)_purple_conversations_hconv_free_key, NULL);
@@ -2565,8 +2568,15 @@ purple_conversations_uninit(void)
 void
 purple_conversations_uninit(void)
 {
+	if (write_conv_cancel != NULL) {
+		g_cancellable_cancel(write_conv_cancel);
+		g_object_unref(write_conv_cancel);
+		write_conv_cancel = NULL;
+	}
+
 	while (conversations)
-		purple_conversation_destroy((PurpleConversation*)conversations->data);
+		purple_conversation_destroy((PurpleConversation *) conversations->data);
+
 	g_hash_table_destroy(conversation_cache);
 	purple_signals_unregister_by_instance(purple_conversations_get_handle());
 }
============================================================
--- configure.ac	0376c94d79e039d1fa217e953c9d52973027e108
+++ configure.ac	1e0cdc95231419582ac72f78df13f2615ee6e7ca
@@ -327,9 +327,9 @@ dnl ####################################
 AM_CONDITIONAL(INSTALL_I18N, test "x$enable_i18n" = "xyes")
 
 dnl #######################################################################
-dnl # Check for GLib 2.12 (required)
+dnl # Check for GLib 2.16 (required)
 dnl #######################################################################
-PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.12.0 gobject-2.0 gmodule-2.0 gthread-2.0], , [
+PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.16 gio-2.0 gobject-2.0 gmodule-2.0 gthread-2.0], , [
 	AC_MSG_RESULT(no)
 	AC_MSG_ERROR([
 
============================================================
--- pidgin/plugins/history.c	651d7f4b1f6c8ac7691e335a36e7d7616ddd2916
+++ pidgin/plugins/history.c	4eb504f741acefd38c410540e6dbea15e7d31a40
@@ -21,170 +21,376 @@
 
 #define HISTORY_SIZE (4 * 1024)
 
-static gboolean _scroll_imhtml_to_end(gpointer data)
-{
-	GtkIMHtml *imhtml = data;
-	gtk_imhtml_scroll_to_end(GTK_IMHTML(imhtml), FALSE);
-	g_object_unref(G_OBJECT(imhtml));
-	return FALSE;
-}
+typedef struct {
+	PurpleAccount *account;
+	PurpleConversation *conv;
+	PurpleLog *log;
+	PurpleLogReadFlags flags;
+	GCancellable *cancel;
+	const gchar *name;
+	const gchar *alias;
+	gulong delete_handle;
+	guint count;
+} _historize_callback_data;
 
-static void historize(PurpleConversation *c)
+static void historize(PurpleConversation *);
+static gboolean _scroll_imhtml_to_end(gpointer);
+static PurpleLog *get_last_log(GList *, PurpleLog *);
+static void historize_log_read_cb(GObject *, GAsyncResult *, gpointer);
+static void historize_log_list_cb(GObject *, GAsyncResult *, gpointer);
+static void historize_log_list_cb_done(_historize_callback_data *);
+static void historize_log_collector_cb(GObject *, GAsyncResult *, gpointer);
+static void historize_log_collector_cb_done(_historize_callback_data *);
+static void historize_stop(PurpleConversation *, gpointer);
+static void historize_log_free(_historize_callback_data *);
+static void history_prefs_check(PurplePlugin *);
+static void history_prefs_cb(const gchar *, PurplePrefType, gconstpointer, gpointer);
+static gboolean plugin_load(PurplePlugin *);
+static void init_plugin(PurplePlugin *);
+
+
+static void
+historize(PurpleConversation *c)
 {
+	_historize_callback_data *callback_data;
 	PurpleAccount *account = purple_conversation_get_account(c);
-	const char *name = purple_conversation_get_name(c);
-	PurpleConversationType convtype;
-	GList *logs = NULL;
-	const char *alias = name;
-	guint flags;
-	char *history;
-	PidginConversation *gtkconv;
-	GtkIMHtmlOptions options = GTK_IMHTML_NO_COLOURS;
-	char *header;
-	char *protocol;
-	char *escaped_alias;
-	const char *header_date;
+	PurpleConversationType convtype = purple_conversation_get_type(c);
+	PidginConversation *gtkconv = PIDGIN_CONVERSATION(c);
+	const gchar *name = purple_conversation_get_name(c), *alias = name;
 
-	convtype = purple_conversation_get_type(c);
-	gtkconv = PIDGIN_CONVERSATION(c);
 	g_return_if_fail(gtkconv != NULL);
+	g_return_if_fail(gtkconv->convs != NULL);
 
-	/* An IM which is the first active conversation. */
-	g_return_if_fail(gtkconv->convs != NULL);
-	if (convtype == PURPLE_CONV_TYPE_IM && !gtkconv->convs->next)
-	{
-		GSList *buddies;
-		GSList *cur;
+	callback_data = g_new0(_historize_callback_data, 1);
+	callback_data->conv = c;
+	callback_data->alias = alias;
+	callback_data->name = name;
+	callback_data->account = account;
+	callback_data->cancel = g_cancellable_new();
 
+	if (convtype == PURPLE_CONV_TYPE_IM && !gtkconv->convs->next) {
+		GSList *buddies, *cur;
+
 		/* If we're not logging, don't show anything.
 		 * Otherwise, we might show a very old log. */
-		if (!purple_prefs_get_bool("/purple/logging/log_ims"))
+		if (!purple_prefs_get_bool("/purple/logging/log_ims")) {
+			g_object_unref(callback_data->cancel);
+			g_free(callback_data);
+
 			return;
+		}
 
 		/* Find buddies for this conversation. */
 		buddies = purple_find_buddies(account, name);
 
 		/* If we found at least one buddy, save the first buddy's alias. */
 		if (buddies != NULL)
-			alias = purple_buddy_get_contact_alias((PurpleBuddy *)buddies->data);
+			alias = purple_buddy_get_contact_alias(PURPLE_BUDDY(buddies->data));
 
-		for (cur = buddies; cur != NULL; cur = cur->next)
-		{
+		for (cur = buddies; cur != NULL; cur = g_slist_next(cur)) {
 			PurpleBlistNode *node = cur->data;
-			PurpleBlistNode *prev = purple_blist_node_get_sibling_prev(node);
-			PurpleBlistNode *next = purple_blist_node_get_sibling_next(node);
-			if ((node != NULL) && ((prev != NULL) || (next != NULL)))
+
+			if (node != NULL &&
+				(purple_blist_node_get_sibling_prev(node) != NULL ||
+				purple_blist_node_get_sibling_next(node) != NULL))
 			{
 				PurpleBlistNode *node2;
 				PurpleBlistNode *parent = purple_blist_node_get_parent(node);
 				PurpleBlistNode *child = purple_blist_node_get_first_child(parent);
 
-				alias = purple_buddy_get_contact_alias((PurpleBuddy *)node);
+				alias = purple_buddy_get_contact_alias(PURPLE_BUDDY(node));
 
 				/* We've found a buddy that matches this conversation.  It's part of a
 				 * PurpleContact with more than one PurpleBuddy.  Loop through the PurpleBuddies
 				 * in the contact and get all the logs. */
-				for (node2 = child ; node2 != NULL ; node2 = purple_blist_node_get_sibling_next(node2))
+				for (node2 = child; node2 != NULL; node2 = purple_blist_node_get_sibling_next(node2))
 				{
-					logs = g_list_concat(purple_log_get_logs(PURPLE_LOG_IM,
-							purple_buddy_get_name((PurpleBuddy *)node2),
-							purple_buddy_get_account((PurpleBuddy *)node2)),
-							logs);
+					PurpleBuddy *buddy = PURPLE_BUDDY(node2);
+
+					callback_data->count++;
+
+					purple_log_get_logs_async(PURPLE_LOG_IM, purple_buddy_get_name(buddy),
+						purple_buddy_get_account(buddy), G_PRIORITY_DEFAULT,
+						callback_data->cancel, historize_log_collector_cb, callback_data);
 				}
+
 				break;
 			}
 		}
-		g_slist_free(buddies);
 
-		if (logs == NULL)
-			logs = purple_log_get_logs(PURPLE_LOG_IM, name, account);
-		else
-			logs = g_list_sort(logs, purple_log_compare);
-	}
-	else if (convtype == PURPLE_CONV_TYPE_CHAT)
-	{
+		g_slist_free(buddies);
+	} else if (convtype == PURPLE_CONV_TYPE_CHAT) {
 		/* If we're not logging, don't show anything.
 		 * Otherwise, we might show a very old log. */
-		if (!purple_prefs_get_bool("/purple/logging/log_chats"))
+		if (!purple_prefs_get_bool("/purple/logging/log_chats")) {
+			g_object_unref(callback_data->cancel);
+			g_free(callback_data);
+
 			return;
+		}
 
-		logs = purple_log_get_logs(PURPLE_LOG_CHAT, name, account);
+		callback_data->count = 1;
+
+		purple_log_get_logs_async(PURPLE_LOG_CHAT, name, account, G_PRIORITY_DEFAULT,
+			callback_data->cancel, historize_log_list_cb, callback_data);
 	}
 
-	if (logs == NULL)
+	callback_data->delete_handle = purple_signal_connect(purple_conversations_get_handle(),
+		"deleting-conversation", c, PURPLE_CALLBACK(historize_stop), callback_data);
+}
+
+static gboolean
+_scroll_imhtml_to_end(gpointer userdata)
+{
+	GtkIMHtml *imhtml = GTK_IMHTML(userdata);
+
+	gtk_imhtml_scroll_to_end(imhtml, FALSE);
+	g_object_unref(imhtml);
+
+	return FALSE;
+}
+
+static PurpleLog *
+get_last_log(GList *list, PurpleLog *last_log)
+{
+	GList *node;
+
+	if (last_log == NULL) {
+		last_log = list->data;
+		node = g_list_next(list);
+	} else
+		node = list;
+
+	for( ; node; node = g_list_next(node))
+		if (purple_log_compare(last_log, node->data) > 0) {
+			purple_log_free(last_log);
+			last_log = node->data;
+		} else
+			purple_log_free(node->data);
+
+	g_list_free(list);
+
+	return last_log;
+}
+
+static void
+historize_log_read_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_historize_callback_data *callback_data = userdata;
+	PurpleLog *log = callback_data->log;
+	PidginConversation *gtkconv = PIDGIN_CONVERSATION(callback_data->conv);
+	GtkIMHtmlOptions options;
+	GtkIMHtml *imhtml = GTK_IMHTML(gtkconv->imhtml);
+	GError *err = NULL;
+	const gchar *header_date;
+	gchar *text, *escaped_alias, *header, *full_header, *text_backup, *protocol, *imhtml_text;
+
+	if (res == NULL) {
+		callback_data->count--;
+
+		if (callback_data->count < 1)
+			historize_log_free(callback_data);
+
 		return;
+	}
 
-	history = purple_log_read((PurpleLog*)logs->data, &flags);
-	gtkconv = PIDGIN_CONVERSATION(c);
-	if (flags & PURPLE_LOG_READ_NO_NEWLINE)
-		options |= GTK_IMHTML_NO_NEWLINE;
+	options = GTK_IMHTML_NO_COLOURS;
+	imhtml_text = gtk_imhtml_get_markup(imhtml);
 
-	protocol = g_strdup(gtk_imhtml_get_protocol_name(GTK_IMHTML(gtkconv->imhtml)));
-	gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml),
-			purple_account_get_protocol_name(((PurpleLog*)logs->data)->account));
+	if (imhtml_text != NULL && *imhtml_text) {
+		text_backup = strdup(imhtml_text);
+		gtk_imhtml_clear(imhtml);
+	} else
+		text_backup = NULL;
 
-	if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml))))
-		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", options);
+	protocol = g_strdup(gtk_imhtml_get_protocol_name(imhtml));
+	gtk_imhtml_set_protocol_name(imhtml,
+		purple_account_get_protocol_name(callback_data->log->account));
 
-	escaped_alias = g_markup_escape_text(alias, -1);
+	if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(imhtml))))
+		gtk_imhtml_append_text(imhtml, "<br>", options);
 
-	if (((PurpleLog *)logs->data)->tm)
-		header_date = purple_date_format_full(((PurpleLog *)logs->data)->tm);
+	if (log->tm)
+		header_date = purple_date_format_full(log->tm);
 	else
-		header_date = purple_date_format_full(localtime(&((PurpleLog *)logs->data)->time));
+		header_date = purple_date_format_full(localtime(&log->time));
 
-	header = g_strdup_printf(_("<b>Conversation with %s on %s:</b><br>"), escaped_alias, header_date);
-	gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), header, options);
+	escaped_alias = g_markup_escape_text(callback_data->alias, -1);
+	full_header = g_strdup_printf(_("Conversation with %s on %s"), escaped_alias, header_date);
+	header = g_strdup_printf("<b>%s:</b><br>", full_header);
+
+	gtk_imhtml_append_text(imhtml, header, options);
+
 	g_free(header);
 	g_free(escaped_alias);
+	g_free(full_header);
 
-	g_strchomp(history);
-	gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), history, options);
-	g_free(history);
+	text = purple_log_read_finish(log, res, &err);
 
-	gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<hr>", options);
+	if (text == NULL)
+		text = err->message;
+	else
+		g_strchomp(text);
 
-	gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), protocol);
+	if (callback_data->flags & PURPLE_LOG_READ_NO_NEWLINE)
+		options |= GTK_IMHTML_NO_NEWLINE;
+
+	//purple_conversation_write?
+	gtk_imhtml_append_text(imhtml, text, options);
+	gtk_imhtml_append_text(imhtml, "<hr>", options);
+	gtk_imhtml_set_protocol_name(imhtml, protocol);
+
+	if (text_backup != NULL) {
+		gtk_imhtml_append_text(imhtml, text_backup, GTK_IMHTML_NO_NEWLINE);
+		g_free(text_backup);
+	}
+
+	g_idle_add(_scroll_imhtml_to_end, g_object_ref(imhtml));
+
 	g_free(protocol);
+	g_clear_error(&err);
 
-	g_object_ref(G_OBJECT(gtkconv->imhtml));
-	g_idle_add(_scroll_imhtml_to_end, gtkconv->imhtml);
+	callback_data->count--;
 
-	g_list_foreach(logs, (GFunc)purple_log_free, NULL);
-	g_list_free(logs);
+	if (callback_data->count < 1)
+		historize_log_free(callback_data);
 }
 
 static void
+historize_log_list_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_historize_callback_data *callback_data = userdata;
+	GList *list;
+	GError *err = NULL;
+
+	if (res == NULL) {
+		callback_data->count--;
+
+		if (callback_data->count < 1)
+			historize_log_list_cb_done(callback_data);
+
+		return;
+	}
+
+	list = purple_log_get_logs_finish(res, &err);
+
+	if (list != NULL)
+		callback_data->log = get_last_log(list, callback_data->log);
+	else if (err != NULL && err->code != G_IO_ERROR_CANCELLED)
+		purple_debug_error("history", "%s\n", err->message);
+
+	g_clear_error(&err);
+
+	callback_data->count--;
+
+	if (callback_data->count < 1)
+		historize_log_list_cb_done(callback_data);
+}
+
+static void
+historize_log_list_cb_done(_historize_callback_data *callback_data)
+{
+	callback_data->count++;
+
+	if (callback_data->log != NULL)
+		purple_log_read_async(callback_data->log, &callback_data->flags,
+			G_PRIORITY_DEFAULT, callback_data->cancel, historize_log_read_cb,
+			callback_data);
+	else
+		historize_log_read_cb(NULL, NULL, callback_data);
+}
+
+static void
+historize_log_collector_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_historize_callback_data *callback_data = userdata;
+	GList *list;
+	GError *err = NULL;
+
+	list = purple_log_get_logs_finish(res, &err);
+
+	if (list != NULL)
+		callback_data->log = get_last_log(list, callback_data->log);
+	else if (err != NULL && err->code != G_IO_ERROR_CANCELLED)
+		purple_debug_error("history", "%s\n", err->message);
+
+	g_clear_error(&err);
+
+	callback_data->count--;
+
+	if (callback_data->count < 1)
+		historize_log_collector_cb_done(userdata);
+}
+
+static void
+historize_log_collector_cb_done(_historize_callback_data *callback_data)
+{
+	callback_data->count++;
+
+	if (callback_data->log == NULL)
+		purple_log_get_logs_async(PURPLE_LOG_IM, callback_data->name,
+			callback_data->account, G_PRIORITY_DEFAULT, callback_data->cancel,
+			historize_log_list_cb, callback_data);
+	else
+		historize_log_list_cb(NULL, NULL, callback_data);
+}
+
+static void
+historize_stop(PurpleConversation *c, gpointer userdata)
+{
+	_historize_callback_data *callback_data = userdata;
+
+	if (callback_data->cancel != NULL)
+		g_cancellable_cancel(callback_data->cancel);
+
+	purple_signal_disconnect(purple_conversations_get_handle(), "deleting-conversation",
+		c, PURPLE_CALLBACK(historize_stop));
+}
+
+static void
+historize_log_free(_historize_callback_data *callback_data)
+{
+	if (callback_data->log != NULL)
+		purple_log_free(callback_data->log);
+
+	/* Why on earth does pidgin give us gulong to refer to our connected signal if it
+	   offers no API to disconnect it with!? */
+	purple_signal_disconnect(purple_conversations_get_handle(), "deleting-conversation",
+		callback_data->conv, PURPLE_CALLBACK(historize_stop));
+
+	g_object_unref(callback_data->cancel);
+	g_free(callback_data);
+}
+
+static void
 history_prefs_check(PurplePlugin *plugin)
 {
 	if (!purple_prefs_get_bool("/purple/logging/log_ims") &&
-	    !purple_prefs_get_bool("/purple/logging/log_chats"))
-	{
+	    !purple_prefs_get_bool("/purple/logging/log_chats")) {
 		purple_notify_warning(plugin, NULL, _("History Plugin Requires Logging"),
-							_("Logging can be enabled from Tools -> Preferences -> Logging.\n\n"
-							  "Enabling logs for instant messages and/or chats will activate "
-							  "history for the same conversation type(s)."));
+			_("Logging can be enabled from Tools -> Preferences -> Logging.\n\n"
+				"Enabling logs for instant messages and/or chats will activate "
+				"history for the same conversation type(s)."));
 	}
 }
 
-static void history_prefs_cb(const char *name, PurplePrefType type,
-							 gconstpointer val, gpointer data)
+static void
+history_prefs_cb(const gchar *name, PurplePrefType type, gconstpointer val, gpointer data)
 {
-	history_prefs_check((PurplePlugin *)data);
+	history_prefs_check((PurplePlugin *) data);
 }
 
 static gboolean
 plugin_load(PurplePlugin *plugin)
 {
 	purple_signal_connect(purple_conversations_get_handle(),
-						"conversation-created",
-						plugin, PURPLE_CALLBACK(historize), NULL);
+		"conversation-created", plugin, PURPLE_CALLBACK(historize), NULL);
 	/* XXX: Do we want to listen to pidgin's "conversation-displayed" signal? */
 
 	purple_prefs_connect_callback(plugin, "/purple/logging/log_ims",
-								history_prefs_cb, plugin);
+		history_prefs_cb, plugin);
 	purple_prefs_connect_callback(plugin, "/purple/logging/log_chats",
-								history_prefs_cb, plugin);
+		history_prefs_cb, plugin);
 
 	history_prefs_check(plugin);
 
============================================================
--- libpurple/log.c	d44c76dbd3a39f8d4b783ef535eb06c60f0368de
+++ libpurple/log.c	a190fb11d41f65733874f305d45724048e02f66a
@@ -36,56 +36,299 @@
 #include "imgstore.h"
 #include "time.h"
 
+
+#if ! GLIB_CHECK_VERSION(2, 19, 8)
+//Fixes strict-aliasing warning
+#undef g_static_mutex_get_mutex_impl_shortcut
+
+#define g_static_mutex_get_mutex_impl_shortcut(mutex) \
+	(g_atomic_pointer_get (mutex) ? *(mutex) : \
+	g_static_mutex_get_mutex_impl (mutex))
+
+#endif
+
+
+typedef struct {
+	PurpleAccount *account;
+	gchar *name;
+} _purple_logsize_user;
+
+typedef struct {
+	PurpleLog *log;
+	GAsyncReadyCallback cb;
+	gpointer userdata;
+} _purple_log_free_callback_data;
+
+typedef struct {
+	PurpleLog *log;
+	GAsyncReadyCallback cb;
+	gpointer userdata;
+} _purple_log_delete_callback_data;
+
+typedef struct {
+	_purple_logsize_user *lu;
+	PurpleLog *log;
+	GAsyncReadyCallback cb;
+	gpointer userdata;
+} _purple_log_write_callback_data;
+
+typedef struct {
+	PurpleLog *log;
+	GAsyncReadyCallback cb;
+	gpointer userdata;
+} _purple_log_read_callback_data;
+
+typedef struct {
+	PurpleLog *log;
+	GAsyncReadyCallback cb;
+	gpointer userdata;
+} _purple_log_size_callback_data;
+
+typedef struct {
+	PurpleLog *log;
+	PurpleMessageFlags type;
+	gchar *from;
+	gchar *message;
+	time_t time;
+} _purple_log_logger_write_callback_data;
+
+typedef struct {
+	PurpleLog *log;
+	PurpleLogReadFlags *flags;
+} _purple_log_logger_read_callback_data;
+
+typedef struct {
+	PurpleAccount *account;
+	PurpleLogType type;
+	gchar *name;
+	gchar *ext;
+} _purple_logger_total_size_callback_data;
+
+typedef struct {
+	PurpleLog *log;
+} _purple_logger_sizer_callback_data;
+
+typedef struct {
+	PurpleLogSetCallback set_cb;
+	GHashTable *sets;
+	GMutex *mutex;
+} _purple_logger_get_sets_common_callback_data;
+
+typedef struct {
+	PurpleLog *log;
+} _purple_logger_deleter_callback_data;
+
+typedef struct {
+	PurpleLog *log;
+	gchar *ext;
+} _purple_logger_writer_callback_data;
+
+typedef struct {
+	PurpleAccount *account;
+	PurpleLogLogger *logger;
+	PurpleLogType type;
+	gchar *name;
+	gchar *ext;
+} _purple_logger_lister_callback_data;
+
+typedef struct {
+	GAsyncReadyCallback cb;
+	GCancellable *cancellable;
+	gpointer userdata;
+	gint io_priority;
+	guint counter;
+	gssize total;
+} _purple_log_total_size_callback_data;
+
+typedef struct {
+	_purple_logsize_user *lu;
+	GAsyncReadyCallback cb;
+	GCancellable *cancel;
+	gpointer userdata;
+	gdouble total;
+	guint count;
+	gint io_priority;
+} _purple_log_activity_score_data;
+
+typedef struct {
+	_purple_log_activity_score_data *score_data;
+	PurpleLog *log;
+} _purple_log_activity_score_size_data;
+
+typedef struct {
+	GAsyncReadyCallback cb;
+	GList *logs;
+	gpointer userdata;
+	guint counter;
+} _purple_log_logs_callback_data;
+
+typedef struct {
+	GAsyncReadyCallback cb;
+	GHashTable *sets;
+	GMutex *mutex;
+	gpointer userdata;
+	guint counter;
+} _purple_log_sets_callback_data;
+
+static void purple_log_logger_write_callback_data_free(gpointer);
+static void purple_logger_writer_callback_data_free(gpointer);
+static void purple_logger_lister_callback_data_free(gpointer);
+static void purple_logger_total_size_callback_data_free(gpointer);
+
+static PurpleLog *purple_log_ref(PurpleLog *);
+static void purple_log_unref(PurpleLog *);
+
+static guint log_set_hash(gconstpointer);
+static gboolean log_set_equal(gconstpointer, gconstpointer);
+static void log_add_log_set_to_hash(GHashTable *, PurpleLogSet *);
+
+static gboolean log_get_log_sets_common(GHashTable *, GMutex *, PurpleLogSetCallback, GError **);
+static void log_get_log_sets_common_thread(GSimpleAsyncResult *, GObject *, GCancellable *);
+static void log_get_log_sets_common_async(GHashTable *, GMutex *, PurpleLogSetCallback,
+	gint, GCancellable *, GAsyncReadyCallback, gpointer);
+
+static void deleter_thread(GSimpleAsyncResult *, GObject *, GCancellable *);
+
+static gchar *process_txt_log(gchar *, gchar *);
+
+static gsize html_logger_write(PurpleLog *, PurpleMessageFlags, const gchar *,
+	time_t, const gchar *);
+static void html_logger_write_async(PurpleLog *, PurpleMessageFlags, const gchar *,
+	time_t, const gchar *, gint, GCancellable *, GAsyncReadyCallback, gpointer);
+static void html_logger_finalize(PurpleLog *);
+static GList *html_logger_list(PurpleLogType, const gchar *, PurpleAccount *);
+static void html_logger_list_async(PurpleLogType, const gchar *, PurpleAccount *,
+	gint, GCancellable *, GAsyncReadyCallback, gpointer);
+static GList *html_logger_list_syslog(PurpleAccount *);
+static void html_logger_list_syslog_async(PurpleAccount *, gint, GCancellable *,
+	GAsyncReadyCallback, gpointer);
+static gchar *html_logger_read(PurpleLog *, PurpleLogReadFlags *);
+static void html_logger_read_thread(GSimpleAsyncResult *, GObject *, GCancellable *);
+static void html_logger_read_async(PurpleLog *, PurpleLogReadFlags *,
+	gint, GCancellable *, GAsyncReadyCallback, gpointer);
+static gint html_logger_total_size(PurpleLogType, const gchar *, PurpleAccount *);
+static void html_logger_total_size_async(PurpleLogType, const gchar *, PurpleAccount *,
+	gint, GCancellable *, GAsyncReadyCallback, gpointer);
+
+static GList *old_logger_list(PurpleLogType, const gchar *, PurpleAccount *);
+static gint old_logger_total_size(PurpleLogType, const gchar *, PurpleAccount *);
+static gchar *old_logger_read(PurpleLog *, PurpleLogReadFlags *);
+static gint old_logger_size(PurpleLog *);
+static void old_logger_get_log_sets(PurpleLogSetCallback, GHashTable *);
+static void old_logger_finalize(PurpleLog *);
+
+static gsize txt_logger_write(PurpleLog *, PurpleMessageFlags, const gchar *, time_t,
+	const gchar *);
+static void txt_logger_write_thread(GSimpleAsyncResult *, GObject *, GCancellable *);
+static void txt_logger_write_async(PurpleLog *, PurpleMessageFlags, const gchar *, time_t,
+	const gchar *, gint, GCancellable *, GAsyncReadyCallback, gpointer);
+static void txt_logger_finalize(PurpleLog *);
+static GList *txt_logger_list(PurpleLogType, const gchar *, PurpleAccount *);
+static void txt_logger_list_async(PurpleLogType, const gchar *, PurpleAccount *,
+	gint, GCancellable *, GAsyncReadyCallback, gpointer);
+static GList *txt_logger_list_syslog(PurpleAccount *);
+static void txt_logger_list_syslog_async(PurpleAccount *, gint, GCancellable *,
+	GAsyncReadyCallback, gpointer);
+static gchar *txt_logger_read(PurpleLog *, PurpleLogReadFlags *);
+static void txt_logger_read_thread(GSimpleAsyncResult *, GObject *, GCancellable *);
+static void txt_logger_read_async(PurpleLog *, PurpleLogReadFlags *, gint, GCancellable *,
+	GAsyncReadyCallback, gpointer);
+static gint txt_logger_total_size(PurpleLogType, const gchar *, PurpleAccount *);
+static void txt_logger_total_size_async(PurpleLogType, const gchar *, PurpleAccount *,
+	gint, GCancellable *, GAsyncReadyCallback, gpointer);
+
+static void log_free_cb(GObject *, GAsyncResult *, gpointer);
+static void log_delete_cb(GObject *, GAsyncResult *, gpointer);
+static void log_write_cb(GObject *, GAsyncResult *, gpointer);
+static void log_read_cb(GObject *, GAsyncResult *, gpointer);
+static void log_size_cb(GObject *, GAsyncResult *, gpointer);
+static void log_list_cb(GObject *, GAsyncResult *, gpointer);
+static void log_system_list_cb(GObject *, GAsyncResult *, gpointer);
+static void log_total_size_cb(GObject *, GAsyncResult *, gpointer);
+static void log_total_size_intermediate_cb(GObject *, GAsyncResult *, gpointer);
+static void log_total_size_list_cb(GObject *, GAsyncResult *, gpointer);
+static void log_hash_cb(GObject *, GAsyncResult *, gpointer);
+static void log_get_activity_score_cb(GObject *, GAsyncResult *, gpointer);
+static void log_get_activity_score_size_cb(GObject *, GAsyncResult *, gpointer);
+
+
+G_LOCK_DEFINE(loggers);
+G_LOCK_DEFINE(current_logger);
+G_LOCK_DEFINE(html_logger);
+G_LOCK_DEFINE(txt_logger);
+G_LOCK_DEFINE(old_logger);
+G_LOCK_DEFINE(logsize_users);
+G_LOCK_DEFINE(logsize_users_decayed);
+G_LOCK_DEFINE(log_ref_table);
+
+
 static GSList *loggers = NULL;
+static PurpleLogLogger *current_logger = NULL;
 
 static PurpleLogLogger *html_logger;
 static PurpleLogLogger *txt_logger;
 static PurpleLogLogger *old_logger;
 
-struct _purple_logsize_user {
-	char *name;
-	PurpleAccount *account;
-};
 static GHashTable *logsize_users = NULL;
 static GHashTable *logsize_users_decayed = NULL;
+static GHashTable *log_ref_table = NULL;
 
-static void log_get_log_sets_common(GHashTable *sets);
 
-static gsize html_logger_write(PurpleLog *log, PurpleMessageFlags type,
-							  const char *from, time_t time, const char *message);
-static void html_logger_finalize(PurpleLog *log);
-static GList *html_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account);
-static GList *html_logger_list_syslog(PurpleAccount *account);
-static char *html_logger_read(PurpleLog *log, PurpleLogReadFlags *flags);
-static int html_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account);
+static void
+purple_log_logger_write_callback_data_free(gpointer userdata)
+{
+	_purple_log_logger_write_callback_data *callback_data = userdata;
 
-static GList *old_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account);
-static int old_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account);
-static char * old_logger_read (PurpleLog *log, PurpleLogReadFlags *flags);
-static int old_logger_size (PurpleLog *log);
-static void old_logger_get_log_sets(PurpleLogSetCallback cb, GHashTable *sets);
-static void old_logger_finalize(PurpleLog *log);
+	g_free(callback_data->from);
+	g_free(callback_data->message);
+	g_free(callback_data);
+}
 
-static gsize txt_logger_write(PurpleLog *log,
-							 PurpleMessageFlags type,
-							 const char *from, time_t time, const char *message);
-static void txt_logger_finalize(PurpleLog *log);
-static GList *txt_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account);
-static GList *txt_logger_list_syslog(PurpleAccount *account);
-static char *txt_logger_read(PurpleLog *log, PurpleLogReadFlags *flags);
-static int txt_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account);
+static void
+purple_logger_writer_callback_data_free(gpointer userdata)
+{
+	_purple_logger_writer_callback_data *callback_data = userdata;
 
+	g_free(callback_data->ext);
+	g_free(callback_data);
+}
+
+static void
+purple_logger_lister_callback_data_free(gpointer userdata)
+{
+	_purple_logger_lister_callback_data *callback_data = userdata;
+
+	g_free(callback_data->name);
+	g_free(callback_data->ext);
+	g_free(callback_data);
+}
+
+static void
+purple_logger_total_size_callback_data_free(gpointer userdata)
+{
+	_purple_logger_total_size_callback_data *callback_data = userdata;
+
+	g_free(callback_data->name);
+	g_free(callback_data->ext);
+	g_free(callback_data);
+}
+
+
 /**************************************************************************
  * PUBLIC LOGGING FUNCTIONS ***********************************************
  **************************************************************************/
 
-PurpleLog *purple_log_new(PurpleLogType type, const char *name, PurpleAccount *account,
-                      PurpleConversation *conv, time_t time, const struct tm *tm)
+PurpleLog *
+purple_log_new(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	PurpleConversation *conv, time_t time, const struct tm *tm)
 {
 	PurpleLog *log;
 
+	g_return_val_if_fail(name != NULL, NULL);
+	g_return_val_if_fail(account != NULL, NULL);
+
+	log = purple_log_ref(g_slice_new(PurpleLog));
 	/* IMPORTANT: Make sure to initialize all the members of PurpleLog */
-	log = g_slice_new(PurpleLog);
 	PURPLE_DBUS_REGISTER_POINTER(log, PurpleLog);
 
 	log->type = type;
@@ -98,8 +341,7 @@ PurpleLog *purple_log_new(PurpleLogType 
 
 	if (tm == NULL)
 		log->tm = NULL;
-	else
-	{
+	else {
 		/* There's no need to zero this as we immediately do a direct copy. */
 		log->tm = g_slice_new(struct tm);
 
@@ -107,9 +349,9 @@ PurpleLog *purple_log_new(PurpleLogType 
 
 #ifdef HAVE_STRUCT_TM_TM_ZONE
 		/* XXX: This is so wrong... */
-		if (log->tm->tm_zone != NULL)
-		{
-			char *tmp = g_locale_from_utf8(log->tm->tm_zone, -1, NULL, NULL, NULL);
+		if (log->tm->tm_zone != NULL) {
+			gchar *tmp = g_locale_from_utf8(log->tm->tm_zone, -1, NULL, NULL, NULL);
+
 			if (tmp != NULL)
 				log->tm->tm_zone = tmp;
 			else
@@ -119,63 +361,115 @@ PurpleLog *purple_log_new(PurpleLogType 
 #endif
 	}
 
+	// TODO: Non-blocking create?
 	if (log->logger && log->logger->create)
-		log->logger->create(log);
+		(log->logger->create)(log);
+
 	return log;
 }
 
-void purple_log_free(PurpleLog *log)
+static PurpleLog *
+purple_log_ref(PurpleLog *log)
 {
-	g_return_if_fail(log);
-	if (log->logger && log->logger->finalize)
-		log->logger->finalize(log);
-	g_free(log->name);
+	guint ref_count;
+	gpointer value;
 
-	if (log->tm != NULL)
-	{
-#ifdef HAVE_STRUCT_TM_TM_ZONE
-		/* XXX: This is so wrong... */
-		g_free((char *)log->tm->tm_zone);
-#endif
-		g_slice_free(struct tm, log->tm);
+	G_LOCK(log_ref_table);
+
+	if (g_hash_table_lookup_extended(log_ref_table, log, NULL, &value)) {
+		ref_count = GPOINTER_TO_UINT(value);
+		ref_count++;
+	} else
+		ref_count = 1;
+
+	g_hash_table_insert(log_ref_table, log, GUINT_TO_POINTER(ref_count));
+
+	G_UNLOCK(log_ref_table);
+
+	return log;
+}
+
+static void
+purple_log_unref(PurpleLog *log)
+{
+	guint ref_count;
+	gpointer value;
+
+	G_LOCK(log_ref_table);
+
+	if (g_hash_table_lookup_extended(log_ref_table, log, NULL, &value)) {
+		ref_count = GPOINTER_TO_UINT(value);
+		ref_count--;
+		g_hash_table_insert(log_ref_table, log, GUINT_TO_POINTER(ref_count));
+	} else
+		// Spit out an error?
+		ref_count = 0;
+
+	G_UNLOCK(log_ref_table);
+
+	if (ref_count < 1) {
+		if (log->logger->async->finalize_async != NULL)
+			log->logger->async->finalize_async(log, G_PRIORITY_LOW, NULL, log_free_cb, log);
+		else if (log->logger->finalize != NULL) {
+			GSimpleAsyncResult *simple;
+
+			log->logger->finalize(log);
+
+			simple = g_simple_async_result_new(NULL, log_free_cb, log, purple_log_free);
+			g_simple_async_result_complete_in_idle(simple);
+			g_object_unref(simple);
+		}
 	}
+}
 
-	PURPLE_DBUS_UNREGISTER_POINTER(log);
-	g_slice_free(PurpleLog, log);
+void
+purple_log_free(PurpleLog *log)
+{
+	g_return_if_fail(log != NULL);
+
+	purple_log_unref(log);
 }
 
-void purple_log_write(PurpleLog *log, PurpleMessageFlags type,
-		    const char *from, time_t time, const char *message)
+void
+purple_log_write(PurpleLog *log, PurpleMessageFlags type, const gchar *from, time_t time,
+	const gchar *message)
 {
-	struct _purple_logsize_user *lu;
-	gsize written, total = 0;
+	_purple_logsize_user *lu;
 	gpointer ptrsize;
+	gsize written, total;
 
-	g_return_if_fail(log);
-	g_return_if_fail(log->logger);
-	g_return_if_fail(log->logger->write);
+	g_return_if_fail(from != NULL);
+	g_return_if_fail(message != NULL);
+	g_return_if_fail(log != NULL);
+	g_return_if_fail(log->logger != NULL);
+	g_return_if_fail(log->logger->write != NULL);
 
 	written = (log->logger->write)(log, type, from, time, message);
 
-	lu = g_new(struct _purple_logsize_user, 1);
+	lu = g_new(_purple_logsize_user, 1);
+	total = 0;
 
 	lu->name = g_strdup(purple_normalize(log->account, log->name));
 	lu->account = log->account;
 
+	G_LOCK(logsize_users);
 	if(g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize)) {
-		char *tmp = lu->name;
+		gchar *tmp = lu->name;
 
 		total = GPOINTER_TO_INT(ptrsize);
 		total += written;
 		g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(total));
+		purple_debug_info("log", "HASH(purple_log_write): total size %i\n", total);
 
 		/* The hash table takes ownership of lu, so create a new one
 		 * for the logsize_users_decayed check below. */
-		lu = g_new(struct _purple_logsize_user, 1);
+		lu = g_new(_purple_logsize_user, 1);
 		lu->name = g_strdup(tmp);
 		lu->account = log->account;
 	}
+	G_UNLOCK(logsize_users);
 
+	G_LOCK(logsize_users_decayed);
 	if(g_hash_table_lookup_extended(logsize_users_decayed, lu, NULL, &ptrsize)) {
 		total = GPOINTER_TO_INT(ptrsize);
 		total += written;
@@ -184,114 +478,437 @@ void purple_log_write(PurpleLog *log, Pu
 		g_free(lu->name);
 		g_free(lu);
 	}
+	G_UNLOCK(logsize_users_decayed);
 }
 
-char *purple_log_read(PurpleLog *log, PurpleLogReadFlags *flags)
+void
+purple_log_write_async(PurpleLog *log, PurpleMessageFlags type, const gchar *from,
+	time_t time, const gchar *message, gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata)
 {
-	PurpleLogReadFlags mflags;
-	g_return_val_if_fail(log && log->logger, NULL);
+	_purple_log_write_callback_data *callback_data;
+	_purple_logsize_user *lu;
+	GSimpleAsyncResult *simple;
+	gssize size;
+
+	g_return_if_fail(message != NULL);
+	g_return_if_fail(from != NULL);
+	g_return_if_fail(log != NULL);
+	g_return_if_fail(log->logger != NULL);
+
+	lu = g_new(_purple_logsize_user, 1);
+
+	lu->name = g_strdup(purple_normalize(log->account, log->name));
+	lu->account = log->account;
+
+	callback_data = g_new0(_purple_log_write_callback_data, 1);
+	callback_data->cb = cb;
+	callback_data->userdata = userdata;
+	callback_data->lu = lu;
+	callback_data->log = log;
+
+	purple_log_ref(log);
+
+	if (log->logger->async->write_async)
+		(log->logger->async->write_async)(log, type, from,
+			time, message, io_priority, cancellable, log_write_cb, callback_data);
+	else if (log->logger->write) {
+		size = (log->logger->write)(log, type, from, time, message);
+
+		simple = g_simple_async_result_new(NULL, log_write_cb, callback_data,
+			purple_log_write_async);
+
+		g_simple_async_result_set_op_res_gssize(simple, size);
+		g_simple_async_result_complete_in_idle(simple);
+
+		g_object_unref(simple);
+	} else {
+		simple = g_simple_async_result_new_error(NULL, log_write_cb, callback_data,
+			G_IO_ERROR, G_IO_ERROR_FAILED, _("The logger has no write function"));
+
+		g_simple_async_result_complete_in_idle(simple);
+
+		g_object_unref(simple);
+	}
+}
+
+gssize
+purple_log_write_finish(PurpleLog *log, GAsyncResult *res, GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = G_SIMPLE_ASYNC_RESULT(res);
+
+	if (g_simple_async_result_propagate_error(simple, error))
+		return -1;
+
+	g_return_val_if_fail(g_simple_async_result_get_source_tag(simple) == purple_log_write_async, -1);
+
+	return g_simple_async_result_get_op_res_gssize(simple);
+}
+
+gchar *
+purple_log_read(PurpleLog *log, PurpleLogReadFlags *flags)
+{
+	gchar *ret;
+
+	g_return_val_if_fail(log != NULL, NULL);
+	g_return_val_if_fail(log->logger != NULL, NULL);
+
 	if (log->logger->read) {
-		char *ret = (log->logger->read)(log, flags ? flags : &mflags);
+		ret = (log->logger->read)(log, flags);
 		purple_str_strip_char(ret, '\r');
+
 		return ret;
 	}
-	return g_strdup(_("<b><font color=\"red\">The logger has no read function</font></b>"));
+
+	return g_strdup_printf("<b><font color=\"red\">%s</font></b>",
+		_("The logger has no read function"));
 }
 
-int purple_log_get_size(PurpleLog *log)
+void
+purple_log_read_async(PurpleLog *log, PurpleLogReadFlags *flags, gint io_priority,
+	GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata)
 {
-	g_return_val_if_fail(log && log->logger, 0);
+	_purple_log_read_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+	gchar *read;
 
+	g_return_if_fail(log != NULL);
+	g_return_if_fail(log->logger != NULL);
+
+	callback_data = g_new0(_purple_log_read_callback_data, 1);
+	callback_data->cb = cb;
+	callback_data->userdata = userdata;
+	callback_data->log = log;
+
+	purple_log_ref(log);
+
+	if (log->logger->async->read_async)
+		(log->logger->async->read_async)(log, flags,
+			io_priority, cancellable, log_read_cb, callback_data);
+	else if (log->logger->read) {
+		read = (log->logger->read)(log, flags);
+
+		simple = g_simple_async_result_new(NULL, log_read_cb, callback_data,
+			purple_log_read_async);
+
+		g_simple_async_result_set_op_res_gpointer(simple, read, g_free);
+		g_simple_async_result_complete_in_idle(simple);
+
+		g_object_unref(simple);
+	} else {
+		simple = g_simple_async_result_new_error(NULL, log_read_cb, callback_data,
+			G_IO_ERROR, G_IO_ERROR_FAILED, "<font color=\"red\"><b>%s</b></font>",
+			_("The logger has no read function"));
+
+		g_simple_async_result_complete_in_idle(simple);
+
+		g_object_unref(simple);
+	}
+}
+
+gchar *
+purple_log_read_finish(PurpleLog *log, GAsyncResult *res, GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = G_SIMPLE_ASYNC_RESULT(res);
+
+	if (g_simple_async_result_propagate_error(simple, error))
+		return NULL;
+
+	g_return_val_if_fail(g_simple_async_result_get_source_tag(simple) == purple_log_read_async, NULL);
+
+	return g_simple_async_result_get_op_res_gpointer(simple);
+}
+
+gint
+purple_log_get_size(PurpleLog *log)
+{
+	g_return_val_if_fail(log != NULL, 0);
+	g_return_val_if_fail(log->logger != NULL, 0);
+
 	if (log->logger->size)
-		return log->logger->size(log);
+		return (log->logger->size)(log);
+
 	return 0;
 }
 
-static guint _purple_logsize_user_hash(struct _purple_logsize_user *lu)
+void
+purple_log_get_size_async(PurpleLog *log, gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata)
 {
+	_purple_log_size_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+
+	g_return_if_fail(log != NULL);
+	g_return_if_fail(log->logger != NULL);
+
+	callback_data = g_new0(_purple_log_size_callback_data, 1);
+	callback_data->cb = cb;
+	callback_data->userdata = userdata;
+	callback_data->log = log;
+
+	purple_log_ref(log);
+
+	if (log->logger->async->size_async)
+		(log->logger->async->size_async)(log, io_priority, cancellable,
+			log_size_cb, callback_data);
+	else if (log->logger->size) {
+		gssize size = (log->logger->size)(log);
+
+		simple = g_simple_async_result_new(NULL, log_size_cb, callback_data,
+			purple_log_get_size_async);
+
+		g_simple_async_result_set_op_res_gssize(simple, size);
+		g_simple_async_result_complete_in_idle(simple);
+
+		g_object_unref(simple);
+	} else {
+		simple = g_simple_async_result_new_error(NULL, log_size_cb, callback_data,
+			G_IO_ERROR, G_IO_ERROR_FAILED, _("Logger has no get-size function"));
+
+		g_simple_async_result_complete_in_idle(simple);
+
+		g_object_unref(simple);
+	}
+}
+
+gssize
+purple_log_get_size_finish(PurpleLog *log, GAsyncResult *res, GError **error)
+{
+	return purple_log_common_sizer_finish(log, res, error);
+}
+
+static guint
+_purple_logsize_user_hash(_purple_logsize_user *lu)
+{
 	return g_str_hash(lu->name);
 }
 
-static guint _purple_logsize_user_equal(struct _purple_logsize_user *lu1,
-		struct _purple_logsize_user *lu2)
+static guint
+_purple_logsize_user_equal(_purple_logsize_user *lu1, _purple_logsize_user *lu2)
 {
-	return (lu1->account == lu2->account && purple_strequal(lu1->name, lu2->name));
+	return lu1->account == lu2->account && purple_strequal(lu1->name, lu2->name);
 }
 
-static void _purple_logsize_user_free_key(struct _purple_logsize_user *lu)
+static void
+_purple_logsize_user_free_key(_purple_logsize_user *lu)
 {
 	g_free(lu->name);
 	g_free(lu);
 }
 
-int purple_log_get_total_size(PurpleLogType type, const char *name, PurpleAccount *account)
+gint
+purple_log_get_total_size(PurpleLogType type, const gchar *name, PurpleAccount *account)
 {
+	_purple_logsize_user *lu;
+	GSList *n;
 	gpointer ptrsize;
-	int size = 0;
-	GSList *n;
-	struct _purple_logsize_user *lu;
+	gboolean result;
+	gint size = 0;
 
-	lu = g_new(struct _purple_logsize_user, 1);
+	g_return_val_if_fail(name != NULL, 0);
+	g_return_val_if_fail(account != NULL, 0);
+
+	lu = g_new(_purple_logsize_user, 1);
 	lu->name = g_strdup(purple_normalize(account, name));
-	lu->account = account;
+	lu->account = account; //g_object_ref?
 
-	if(g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize)) {
+	G_LOCK(logsize_users);
+	result = g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize);
+	G_UNLOCK(logsize_users);
+
+	if(result) {
 		size = GPOINTER_TO_INT(ptrsize);
+		purple_debug_info("log", "HASH(purple_log_get_total_size): using size from hash %i {name = \"%s\"\n}", size, name);
+
 		g_free(lu->name);
 		g_free(lu);
 	} else {
-		for (n = loggers; n; n = n->next) {
+		for (n = purple_log_logger_get_all(); n; n = g_slist_next(n)) {
 			PurpleLogLogger *logger = n->data;
 
 			if(logger->total_size){
 				size += (logger->total_size)(type, name, account);
 			} else if(logger->list) {
 				GList *logs = (logger->list)(type, name, account);
-				int this_size = 0;
 
-				while (logs) {
-					PurpleLog *log = (PurpleLog*)(logs->data);
-					this_size += purple_log_get_size(log);
+				for ( ; logs != NULL; logs = g_list_delete_link(logs, logs)) {
+					PurpleLog *log = logs->data;
+					size += purple_log_get_size(log);
 					purple_log_free(log);
-					logs = g_list_delete_link(logs, logs);
 				}
-
-				size += this_size;
 			}
 		}
 
+		purple_debug_info("log", "HASH(purple_log_get_total_size): write size to hash %i {name = \"%s\"\n}", size, name);
+
+		G_LOCK(logsize_users);
 		g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(size));
+		G_LOCK(logsize_users);
 	}
+
 	return size;
 }
 
-gint purple_log_get_activity_score(PurpleLogType type, const char *name, PurpleAccount *account)
+// TODO: Would it make sense to allow the caller to pass in a list of logs, if
+// TODO: it just got them from purple_log_get_logs_async()?  Pidgin asks
+// TODO: for the total size, which means that for some loggers, we end up
+// TODO: calling list *again* needlessly (to loop over them and size them).
+// TODO: If we had a list of logs, we could loop over them and find those
+// TODO: where the logger had a size function (but no total_size function).
+// TODO: We could size just those with something like this (this ignores
+// TODO: the blocking vs. non-blocking distinction for simplicity):
+// TODO: for (...) {
+// TODO: 	if (!log->logger->total_size && log->logger->size)
+// TODO: 		Call the size function.
+void
+purple_log_get_total_size_async(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata)
 {
-	gpointer ptrscore;
-	int score;
+	_purple_log_total_size_callback_data *callback_data;
+	_purple_logsize_user *lu;
+	GSimpleAsyncResult *simple;
 	GSList *n;
-	struct _purple_logsize_user *lu;
+	gpointer ptrsize;
+	gboolean result;
+	gssize size = 0;
+
+	g_return_if_fail(name != NULL);
+	g_return_if_fail(account != NULL);
+
+	lu = g_new(_purple_logsize_user, 1);
+	lu->name = g_strdup(purple_normalize(account, name));
+	lu->account = account; //g_object_ref?
+
+	callback_data = g_new0(_purple_log_total_size_callback_data, 1);
+	callback_data->cb = cb;
+	callback_data->userdata = userdata;
+	callback_data->total = 0;
+
+	G_LOCK(logsize_users);
+	result = g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize);
+	G_UNLOCK(logsize_users);
+
+	if (result) {
+		size = GPOINTER_TO_INT(ptrsize);
+
+		g_free(lu->name);
+		g_free(lu);
+
+		callback_data->counter = 1;
+
+		simple = g_simple_async_result_new(NULL, log_total_size_list_cb,
+			callback_data, purple_log_common_total_sizer_async);
+
+		g_simple_async_result_set_op_res_gssize(simple, size);
+		g_simple_async_result_complete_in_idle(simple);
+
+		g_object_unref(simple);
+
+		return;
+	}
+
+	g_free(lu->name);
+	g_free(lu);
+
+	n = purple_log_logger_get_all();
+	callback_data->counter = g_slist_length(n);
+	callback_data->cancellable = g_object_ref(cancellable);
+	callback_data->io_priority = io_priority;
+
+	for ( ; n; n = g_slist_next(n)) {
+		PurpleLogLogger *logger = n->data;
+
+		if (logger->async->total_size_async)
+			(logger->async->total_size_async)(type, name, account, io_priority,
+				cancellable, log_total_size_cb, callback_data);
+		else if (logger->async->list_async)
+			/* List the logs and manually size them all up */
+			(logger->async->list_async)(type, name, account, io_priority,
+				cancellable, log_total_size_list_cb, callback_data);
+		else if (logger->total_size) {
+			size = (logger->total_size)(type, name, account);
+
+			simple = g_simple_async_result_new(NULL, log_total_size_cb, callback_data,
+				purple_log_common_total_sizer_async);
+
+			g_simple_async_result_set_op_res_gssize(simple, size);
+			g_simple_async_result_complete_in_idle(simple);
+
+			g_object_unref(simple);
+		} else if (logger->list) {
+			/* List the logs and manually size them all up */
+			GList *logs = (logger->list)(type, name, account);
+
+			simple = g_simple_async_result_new(NULL, log_total_size_list_cb, callback_data,
+				purple_log_common_lister_async);
+
+			g_simple_async_result_set_op_res_gpointer(simple, logs, (GDestroyNotify) g_list_free);
+			g_simple_async_result_complete_in_idle(simple);
+
+			g_object_unref(simple);
+		} else {
+			simple = g_simple_async_result_new_error(NULL, log_total_size_list_cb, callback_data,
+				G_IO_ERROR, G_IO_ERROR_FAILED, _("Logger has no get-size or list function"));
+
+			g_simple_async_result_complete_in_idle(simple);
+
+			g_object_unref(simple);
+		}
+	}
+}
+
+gssize
+purple_log_get_total_size_finish(GAsyncResult *res, GError **error)
+{
+	return purple_log_common_total_sizer_finish(res, &err);
+}
+
+gint
+purple_log_get_activity_score(PurpleLogType type, const gchar *name, PurpleAccount *account)
+{
+	_purple_logsize_user *lu;
+	PurpleLogLogger *logger;
+	PurpleLog *log;
+	GList *logs;
+	GSList *n;
+	gpointer ptrscore;
 	time_t now;
+	gboolean result;
+	gdouble score_double;
+	gint score;
+
+	g_return_val_if_fail(name != NULL, 0);
+	g_return_val_if_fail(account != NULL, 0);
+
 	time(&now);
 
-	lu = g_new(struct _purple_logsize_user, 1);
+	lu = g_new(_purple_logsize_user, 1);
 	lu->name = g_strdup(purple_normalize(account, name));
-	lu->account = account;
+	lu->account = account; //g_object_ref?
 
-	if(g_hash_table_lookup_extended(logsize_users_decayed, lu, NULL, &ptrscore)) {
+	G_LOCK(logsize_users_decayed);
+	result = g_hash_table_lookup_extended(logsize_users_decayed, lu, NULL, &ptrscore);
+	G_UNLOCK(logsize_users_decayed);
+
+	if (result) {
 		score = GPOINTER_TO_INT(ptrscore);
 		g_free(lu->name);
 		g_free(lu);
 	} else {
-		double score_double = 0.0;
-		for (n = loggers; n; n = n->next) {
-			PurpleLogLogger *logger = n->data;
+		score_double = 0.0;
 
+		for (n = purple_log_logger_get_all(); n; n = g_slist_next(n)) {
+			logger = n->data;
+
 			if(logger->list) {
-				GList *logs = (logger->list)(type, name, account);
+				logs = (logger->list)(type, name, account);
 
 				while (logs) {
-					PurpleLog *log = (PurpleLog*)(logs->data);
+					log = logs->data;
 					/* Activity score counts bytes in the log, exponentially
 					   decayed with a half-life of 14 days. */
 					score_double += purple_log_get_size(log) *
@@ -303,17 +920,90 @@ gint purple_log_get_activity_score(Purpl
 		}
 
 		score = (gint) ceil(score_double);
+
+		G_LOCK(logsize_users_decayed);
 		g_hash_table_replace(logsize_users_decayed, lu, GINT_TO_POINTER(score));
+		G_UNLOCK(logsize_users_decayed);
 	}
+
 	return score;
 }
 
-gboolean purple_log_is_deletable(PurpleLog *log)
+void
+purple_log_get_activity_score_async(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata)
 {
+	_purple_log_activity_score_data *callback_data;
+	_purple_logsize_user *lu;
+	GSimpleAsyncResult *simple;
+	gpointer ptrscore;
+	gint score;
+	gboolean result;
+
+	g_return_if_fail(name != NULL);
+	g_return_if_fail(account != NULL);
+
+	lu = g_new(_purple_logsize_user, 1);
+	lu->name = g_strdup(purple_normalize(account, name));
+	lu->account = account; //g_object_ref?
+
+	G_LOCK(logsize_users_decayed);
+	result = g_hash_table_lookup_extended(logsize_users_decayed, lu, NULL, &ptrscore);
+	G_UNLOCK(logsize_users_decayed);
+
+	if (result) {
+		score = GPOINTER_TO_INT(ptrscore);
+
+		g_free(lu->name);
+		g_free(lu);
+
+		simple = g_simple_async_result_new(NULL, cb,
+			userdata, purple_log_get_activity_score_async);
+
+		g_simple_async_result_set_op_res_gssize(simple, score);
+		g_simple_async_result_complete_in_idle(simple);
+
+		g_object_unref(simple);
+
+		return;
+	}
+
+	callback_data = g_new0(_purple_log_activity_score_data, 1);
+	callback_data->cb = cb;
+	callback_data->userdata = userdata;
+	callback_data->lu = lu;
+	callback_data->cancel = cancellable;
+	callback_data->io_priority = io_priority;
+
+	if (cancellable != NULL)
+		g_object_ref(cancellable);
+
+	purple_log_get_logs_async(type, name, account, io_priority, cancellable,
+		log_get_activity_score_cb, callback_data);
+}
+
+gint
+purple_log_get_activity_score_finish(GAsyncResult *res, GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = G_SIMPLE_ASYNC_RESULT(res);
+
+	if (g_simple_async_result_propagate_error(simple, error))
+		return FALSE;
+
+	g_return_val_if_fail(g_simple_async_result_get_source_tag(simple) != purple_log_get_activity_score_async, -1);
+
+	return g_simple_async_result_get_op_res_gssize(simple);
+}
+
+gboolean
+purple_log_is_deletable(PurpleLog *log)
+{
 	g_return_val_if_fail(log != NULL, FALSE);
 	g_return_val_if_fail(log->logger != NULL, FALSE);
 
-	if (log->logger->remove == NULL)
+	if (log->logger->remove == NULL && log->logger->async->remove_async == NULL)
 		return FALSE;
 
 	if (log->logger->is_deletable != NULL)
@@ -322,7 +1012,8 @@ gboolean purple_log_is_deletable(PurpleL
 	return TRUE;
 }
 
-gboolean purple_log_delete(PurpleLog *log)
+gboolean
+purple_log_delete(PurpleLog *log)
 {
 	g_return_val_if_fail(log != NULL, FALSE);
 	g_return_val_if_fail(log->logger != NULL, FALSE);
@@ -333,27 +1024,81 @@ gboolean purple_log_delete(PurpleLog *lo
 	return FALSE;
 }
 
-char *
-purple_log_get_log_dir(PurpleLogType type, const char *name, PurpleAccount *account)
+void
+purple_log_delete_async(PurpleLog *log, gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata)
 {
+	_purple_log_delete_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+	gboolean result;
+
+	g_return_if_fail(log != NULL);
+	g_return_if_fail(log->logger != NULL);
+
+	callback_data = g_new0(_purple_log_delete_callback_data, 1);
+	callback_data->cb = cb;
+	callback_data->userdata = userdata;
+	callback_data->log = log;
+
+	if (log->logger->async->remove_async != NULL)
+		(log->logger->async->remove_async)(log, io_priority, cancellable,
+			log_delete_cb, callback_data);
+	else {
+		/* As there is no nonblocking function we can call blocking analog */
+		result = FALSE;
+
+		if (log->logger->remove != NULL)
+			result = (log->logger->remove)(log);
+
+		simple = g_simple_async_result_new(NULL, cb, userdata, purple_log_delete_async);
+
+		g_simple_async_result_set_op_res_gboolean(simple, result);
+		g_simple_async_result_complete_in_idle(simple);
+
+		g_object_unref(simple);
+	}
+}
+
+gboolean
+purple_log_delete_finish(PurpleLog *log, GAsyncResult *res, GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = G_SIMPLE_ASYNC_RESULT(res);
+
+	if (g_simple_async_result_propagate_error(simple, error))
+		return FALSE;
+
+	if (g_simple_async_result_get_source_tag(simple) == purple_log_delete_async)
+		return g_simple_async_result_get_op_res_gboolean(simple);
+	else
+		return purple_log_common_deleter_finish(log, res, error);
+}
+
+gchar *
+purple_log_get_log_dir(PurpleLogType type, const gchar *name, PurpleAccount *account)
+{
 	PurplePlugin *prpl;
 	PurplePluginProtocolInfo *prpl_info;
-	const char *prpl_name;
-	char *acct_name;
-	const char *target;
-	char *dir;
+	const gchar *prpl_name, *target;
+	gchar *acct_name, *dir, *temp;
 
+	g_return_val_if_fail(name != NULL, NULL);
+	g_return_val_if_fail(account != NULL, NULL);
+
 	prpl = purple_find_prpl(purple_account_get_protocol_id(account));
+
 	if (!prpl)
 		return NULL;
+
 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
 	prpl_name = prpl_info->list_icon(account, NULL);
 
 	acct_name = g_strdup(purple_escape_filename(purple_normalize(account,
-				purple_account_get_username(account))));
+		purple_account_get_username(account))));
 
 	if (type == PURPLE_LOG_CHAT) {
-		char *temp = g_strdup_printf("%s.chat", purple_normalize(account, name));
+		temp = g_strdup_printf("%s.chat", purple_normalize(account, name));
 		target = purple_escape_filename(temp);
 		g_free(temp);
 	} else if(type == PURPLE_LOG_SYSTEM) {
@@ -362,7 +1107,10 @@ purple_log_get_log_dir(PurpleLogType typ
 		target = purple_escape_filename(purple_normalize(account, name));
 	}
 
-	dir = g_build_filename(purple_user_dir(), "logs", prpl_name, acct_name, target, NULL);
+	if (strlen(target) > 0)
+		dir = g_build_filename(purple_user_dir(), "logs", prpl_name, acct_name, target, NULL);
+	else
+		dir = NULL;
 
 	g_free(acct_name);
 
@@ -373,40 +1121,30 @@ purple_log_get_log_dir(PurpleLogType typ
  * LOGGER FUNCTIONS *********************************************************
  ****************************************************************************/
 
-static PurpleLogLogger *current_logger = NULL;
-
-static void logger_pref_cb(const char *name, PurplePrefType type,
-						   gconstpointer value, gpointer data)
+static void
+logger_pref_cb(const gchar *name, PurplePrefType type, gconstpointer value, gpointer data)
 {
 	PurpleLogLogger *logger;
-	GSList *l = loggers;
-	while (l) {
+	GSList *l;
+
+	for (l = purple_log_logger_get_all(); l; l = g_slist_next(l)) {
 		logger = l->data;
+
 		if (purple_strequal(logger->id, value)) {
 			purple_log_logger_set(logger);
 			return;
 		}
-		l = l->next;
 	}
+
+	G_LOCK(txt_logger);
 	purple_log_logger_set(txt_logger);
+	G_UNLOCK(txt_logger);
 }
 
 
-PurpleLogLogger *purple_log_logger_new(const char *id, const char *name, int functions, ...)
+PurpleLogLogger *
+purple_log_logger_new(const gchar *id, const gchar *name, gint functions, ...)
 {
-#if 0
-				void(*create)(PurpleLog *),
-				gsize(*write)(PurpleLog *, PurpleMessageFlags, const char *, time_t, const char *),
-				void(*finalize)(PurpleLog *),
-				GList*(*list)(PurpleLogType type, const char*, PurpleAccount*),
-				char*(*read)(PurpleLog*, PurpleLogReadFlags*),
-				int(*size)(PurpleLog*),
-				int(*total_size)(PurpleLogType type, const char *name, PurpleAccount *account),
-				GList*(*list_syslog)(PurpleAccount *account),
-				void(*get_log_sets)(PurpleLogSetCallback cb, GHashTable *sets),
-				gboolean(*remove)(PurpleLog *log),
-				gboolean(*is_deletable)(PurpleLog *log))
-#endif
 	PurpleLogLogger *logger;
 	va_list args;
 
@@ -443,7 +1181,31 @@ PurpleLogLogger *purple_log_logger_new(c
 	if (functions >= 11)
 		logger->is_deletable = va_arg(args, void *);
 
+	logger->async = g_new0(PurpleLogLoggerAsyncFuncs, 1);
+
+	/* callbacks functions */
 	if (functions >= 12)
+		logger->async->create_async = va_arg(args, void *);
+	if (functions >= 13)
+		logger->async->write_async = va_arg(args, void *);
+	if (functions >= 14)
+		logger->async->finalize_async = va_arg(args, void *);
+	if (functions >= 15)
+		logger->async->list_async = va_arg(args, void *);
+	if (functions >= 16)
+		logger->async->read_async = va_arg(args, void *);
+	if (functions >= 17)
+		logger->async->size_async = va_arg(args, void *);
+	if (functions >= 18)
+		logger->async->total_size_async = va_arg(args, void *);
+	if (functions >= 19)
+		logger->async->list_syslog_async = va_arg(args, void *);
+	if (functions >= 20)
+		logger->async->get_log_sets_async = va_arg(args, void *);
+	if (functions >= 21)
+		logger->async->remove_async = va_arg(args, void *);
+
+	if (functions >= 22)
 		purple_debug_info("log", "Dropping new functions for logger: %s (%s)\n", name, id);
 
 	va_end(args);
@@ -451,51 +1213,89 @@ PurpleLogLogger *purple_log_logger_new(c
 	return logger;
 }
 
-void purple_log_logger_free(PurpleLogLogger *logger)
+void
+purple_log_logger_free(PurpleLogLogger *logger)
 {
+	g_return_if_fail(logger != NULL);
+
 	g_free(logger->name);
 	g_free(logger->id);
+	g_free(logger->async);
 	g_free(logger);
 }
 
-void purple_log_logger_add (PurpleLogLogger *logger)
+void
+purple_log_logger_add(PurpleLogLogger *logger)
 {
-	g_return_if_fail(logger);
+	g_return_if_fail(logger != NULL);
+
+	G_LOCK(loggers);
 	if (g_slist_find(loggers, logger))
 		return;
+
 	loggers = g_slist_append(loggers, logger);
-	if (purple_strequal(purple_prefs_get_string("/purple/logging/format"), logger->id)) {
+	G_UNLOCK(loggers);
+
+	if (purple_strequal(purple_prefs_get_string("/purple/logging/format"), logger->id))
 		purple_prefs_trigger_callback("/purple/logging/format");
-	}
 }
 
-void purple_log_logger_remove (PurpleLogLogger *logger)
+void
+purple_log_logger_remove(PurpleLogLogger *logger)
 {
-	g_return_if_fail(logger);
+	g_return_if_fail(logger != NULL);
+
+	G_LOCK(loggers);
 	loggers = g_slist_remove(loggers, logger);
+	G_UNLOCK(loggers);
 }
 
-void purple_log_logger_set (PurpleLogLogger *logger)
+void
+purple_log_logger_set(PurpleLogLogger *logger)
 {
-	g_return_if_fail(logger);
+	g_return_if_fail(logger != NULL);
+
+	G_LOCK(current_logger);
 	current_logger = logger;
+	G_UNLOCK(current_logger);
 }
 
-PurpleLogLogger *purple_log_logger_get()
+PurpleLogLogger *
+purple_log_logger_get(void)
 {
-	return current_logger;
+	PurpleLogLogger *logger;
+
+	G_LOCK(current_logger);
+	logger = current_logger;
+	G_UNLOCK(current_logger);
+
+	return logger;
 }
 
-GList *purple_log_logger_get_options(void)
+GSList *
+purple_log_logger_get_all(void)
 {
+	GSList *list;
+
+	G_LOCK(loggers);
+	list = loggers;
+	G_UNLOCK(loggers);
+
+	return list;
+}
+
+GList *
+purple_log_logger_get_options(void)
+{
 	GSList *n;
 	GList *list = NULL;
-	PurpleLogLogger *data;
 
-	for (n = loggers; n; n = n->next) {
-		data = n->data;
-		if (!data->write)
+	for (n = purple_log_logger_get_all(); n; n = g_slist_next(n)) {
+		PurpleLogLogger *data = n->data;
+
+		if (!data->write && !data->async->write_async)
 			continue;
+
 		list = g_list_append(list, data->name);
 		list = g_list_append(list, data->id);
 	}
@@ -503,32 +1303,106 @@ GList *purple_log_logger_get_options(voi
 	return list;
 }
 
-gint purple_log_compare(gconstpointer y, gconstpointer z)
+gint
+purple_log_compare(gconstpointer y, gconstpointer z)
 {
-	const PurpleLog *a = y;
-	const PurpleLog *b = z;
+	const PurpleLog *a = y, *b = z;
 
 	return b->time - a->time;
 }
 
-GList *purple_log_get_logs(PurpleLogType type, const char *name, PurpleAccount *account)
+GList *
+purple_log_get_logs(PurpleLogType type, const gchar *name, PurpleAccount *account)
 {
 	GList *logs = NULL;
 	GSList *n;
-	for (n = loggers; n; n = n->next) {
+
+	g_return_val_if_fail(name != NULL, NULL);
+	g_return_val_if_fail(account != NULL, NULL);
+
+	for (n = purple_log_logger_get_all(); n; n = g_slist_next(n)) {
 		PurpleLogLogger *logger = n->data;
+
 		if (!logger->list)
 			continue;
+
 		logs = g_list_concat(logger->list(type, name, account), logs);
 	}
 
 	return g_list_sort(logs, purple_log_compare);
 }
 
-gint purple_log_set_compare(gconstpointer y, gconstpointer z)
+void
+purple_log_get_logs_async(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata)
 {
-	const PurpleLogSet *a = y;
-	const PurpleLogSet *b = z;
+	_purple_log_logs_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+	GSList *n;
+
+	g_return_if_fail(name != NULL);
+	g_return_if_fail(account != NULL);
+
+	n = purple_log_logger_get_all();
+
+	callback_data = g_new0(_purple_log_logs_callback_data, 1);
+	callback_data->cb = cb;
+	callback_data->userdata = userdata;
+	callback_data->counter = g_slist_length(n);
+	callback_data->logs = NULL;
+
+	for ( ; n; n = g_slist_next(n)) {
+		PurpleLogLogger *logger = n->data;
+
+		if (logger->async->list_async)
+			(logger->async->list_async)(type, name, account, io_priority, cancellable,
+				log_list_cb, callback_data);
+		else if (logger->list) {
+			/* Call the blocking list function instead. */
+			GList *logs = (logger->list)(type, name, account);
+
+			simple = g_simple_async_result_new(NULL,
+				log_list_cb,
+				callback_data,
+				purple_log_common_lister_async);
+			g_simple_async_result_set_op_res_gpointer(simple, logs, NULL);
+
+			g_simple_async_result_complete_in_idle(simple);
+
+			g_object_unref(simple);
+		} else {
+			simple = g_simple_async_result_new_error(NULL, log_list_cb,
+				callback_data,
+				G_FILE_ERROR,
+				G_FILE_ERROR_FAILED,
+				_("No available non-blocking log-getting function"));
+
+			g_simple_async_result_complete_in_idle(simple);
+
+			g_object_unref(simple);
+		}
+	}
+}
+
+GList *
+purple_log_get_logs_finish(GAsyncResult *res, GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = G_SIMPLE_ASYNC_RESULT(res);
+
+	if (g_simple_async_result_propagate_error(simple, error))
+		return NULL;
+
+	g_return_val_if_fail(g_simple_async_result_get_source_tag(simple) == purple_log_common_lister_async, NULL);
+
+	return g_simple_async_result_get_op_res_gpointer(simple);
+}
+
+gint
+purple_log_set_compare(gconstpointer y, gconstpointer z)
+{
+	const PurpleLogSet *a = y, *b = z;
 	gint ret = 0;
 
 	/* This logic seems weird at first...
@@ -538,15 +1412,17 @@ gint purple_log_set_compare(gconstpointe
 	 * doesn't. */
 	if (a->account != NULL && b->account != NULL) {
 		ret = strcmp(purple_account_get_username(a->account), purple_account_get_username(b->account));
+
 		if (ret != 0)
 			return ret;
 	}
 
 	ret = strcmp(a->normalized_name, b->normalized_name);
+
 	if (ret != 0)
 		return ret;
 
-	return (gint)b->type - (gint)a->type;
+	return (gint) b->type - (gint) a->type;
 }
 
 static guint
@@ -582,14 +1458,17 @@ log_add_log_set_to_hash(GHashTable *sets
 		purple_log_set_free(set);
 }
 
-GHashTable *purple_log_get_log_sets(void)
+GHashTable *
+purple_log_get_log_sets(void)
 {
+	GHashTable *sets;
 	GSList *n;
-	GHashTable *sets = g_hash_table_new_full(log_set_hash, log_set_equal,
-											 (GDestroyNotify)purple_log_set_free, NULL);
 
+	sets = g_hash_table_new_full(log_set_hash, log_set_equal,
+		(GDestroyNotify) purple_log_set_free, NULL);
+
 	/* Get the log sets from all the loggers. */
-	for (n = loggers; n; n = n->next) {
+	for (n = purple_log_logger_get_all(); n; n = g_slist_next(n)) {
 		PurpleLogLogger *logger = n->data;
 
 		if (!logger->get_log_sets)
@@ -598,52 +1477,180 @@ GHashTable *purple_log_get_log_sets(void
 		logger->get_log_sets(log_add_log_set_to_hash, sets);
 	}
 
-	log_get_log_sets_common(sets);
+	log_get_log_sets_common(sets, NULL, log_add_log_set_to_hash, NULL);
 
 	/* Return the GHashTable of unique PurpleLogSets. */
 	return sets;
 }
 
-void purple_log_set_free(PurpleLogSet *set)
+void
+purple_log_get_log_sets_async(gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata)
 {
+	_purple_log_sets_callback_data *callback_data;
+	GSList *n;
+
+	callback_data = g_new0(_purple_log_sets_callback_data, 1);
+	callback_data->cb = cb;
+	callback_data->mutex = g_mutex_new();
+	callback_data->userdata = userdata;
+	callback_data->sets = g_hash_table_new_full(log_set_hash, log_set_equal,
+		(GDestroyNotify) purple_log_set_free, NULL);
+
+	/* +1 is need special for log_get_log_sets_common_async call */
+	n = purple_log_logger_get_all();
+	callback_data->counter = g_slist_length(n) + 1;
+
+	/* Get the log sets from all the loggers. */
+	for ( ; n; n = g_slist_next(n)) {
+		PurpleLogLogger *logger = n->data;
+
+		if (logger->async->get_log_sets_async)
+			(logger->async->get_log_sets_async)(log_add_log_set_to_hash, callback_data->sets,
+				io_priority, cancellable, log_hash_cb, callback_data);
+		else if (logger->get_log_sets) {
+			GSimpleAsyncResult *simple;
+
+			/* As there is no nonblocking  function we can call blocking analog */
+			g_mutex_lock(callback_data->mutex);
+			(logger->get_log_sets)(log_add_log_set_to_hash, callback_data->sets);
+			g_mutex_unlock(callback_data->mutex);
+
+			simple = g_simple_async_result_new(NULL, log_hash_cb, callback_data,
+				log_get_log_sets_common_async);
+
+			g_hash_table_ref(callback_data->sets);
+			g_simple_async_result_set_op_res_gpointer(simple, callback_data->sets,
+				(GDestroyNotify) g_hash_table_unref);
+			g_simple_async_result_complete_in_idle(simple);
+
+			g_object_unref(simple);
+		} else
+			callback_data->counter--;
+	}
+
+	log_get_log_sets_common_async(callback_data->sets, callback_data->mutex,
+		log_add_log_set_to_hash, io_priority, cancellable, log_hash_cb, callback_data);
+}
+
+GHashTable *
+purple_log_get_log_sets_finish(GAsyncResult *res, GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = G_SIMPLE_ASYNC_RESULT(res);
+
+	if (g_simple_async_result_propagate_error(simple, error))
+		return NULL;
+
+	g_return_val_if_fail(g_simple_async_result_get_source_tag(simple) == log_get_log_sets_common_async, NULL);
+
+	return g_hash_table_ref(g_simple_async_result_get_op_res_gpointer(simple));
+}
+
+void
+purple_log_set_free(PurpleLogSet *set)
+{
 	g_return_if_fail(set != NULL);
 
 	g_free(set->name);
+
 	if (set->normalized_name != set->name)
 		g_free(set->normalized_name);
 
 	g_slice_free(PurpleLogSet, set);
 }
 
-GList *purple_log_get_system_logs(PurpleAccount *account)
+GList *
+purple_log_get_system_logs(PurpleAccount *account)
 {
 	GList *logs = NULL;
 	GSList *n;
-	for (n = loggers; n; n = n->next) {
+
+	g_return_val_if_fail(account != NULL, NULL);
+
+	for (n = purple_log_logger_get_all(); n; n = g_slist_next(n)) {
 		PurpleLogLogger *logger = n->data;
+
 		if (!logger->list_syslog)
 			continue;
+
 		logs = g_list_concat(logger->list_syslog(account), logs);
 	}
 
 	return g_list_sort(logs, purple_log_compare);
 }
 
+void
+purple_log_get_system_logs_async(PurpleAccount *account, gint io_priority,
+	GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata)
+{
+	_purple_log_logs_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+	GSList *n;
+
+	g_return_if_fail(account != NULL);
+
+	n = purple_log_logger_get_all();
+
+	callback_data = g_new0(_purple_log_logs_callback_data, 1);
+	callback_data->userdata = userdata;
+	callback_data->cb = cb;
+	callback_data->counter = g_slist_length(n);
+	callback_data->logs = NULL;
+
+	for ( ; n; n = g_slist_next(n)) {
+		PurpleLogLogger *logger = n->data;
+
+		if (logger->async->list_syslog_async)
+			(logger->async->list_syslog_async)(account, io_priority, cancellable,
+				log_system_list_cb, callback_data);
+		else if (logger->list_syslog) {
+			/* Call the blocking list function instead. */
+			GList *logs = (logger->list_syslog)(account);
+
+			simple = g_simple_async_result_new(NULL,
+				log_system_list_cb,
+				callback_data,
+				purple_log_common_lister_async);
+
+			g_simple_async_result_set_op_res_gpointer(simple, logs, NULL);
+			g_simple_async_result_complete_in_idle(simple);
+
+			g_object_unref(simple);
+		} else {
+			simple = g_simple_async_result_new_error(NULL, log_system_list_cb,
+				callback_data,
+				G_FILE_ERROR,
+				G_FILE_ERROR_FAILED,
+				_("No available non-blocking system-log-getting function"));
+
+			g_simple_async_result_complete_in_idle(simple);
+
+			g_object_unref(simple);
+		}
+	}
+}
+
+GList *
+purple_log_get_system_logs_finish(GAsyncResult *res, GError **error)
+{
+	return purple_log_common_lister_finish(res, error);
+}
+
 /****************************************************************************
  * LOG SUBSYSTEM ************************************************************
  ****************************************************************************/
 
-void *
-purple_log_get_handle(void)
+void
+purple_log_init(void)
 {
-	static int handle;
+	void *handle;
 
-	return &handle;
-}
+	if (!g_thread_supported())
+		g_thread_init(NULL);
 
-void purple_log_init(void)
-{
-	void *handle = purple_log_get_handle();
+	handle = purple_log_get_handle();
 
 	purple_prefs_add_none("/purple/logging");
 	purple_prefs_add_bool("/purple/logging/log_ims", TRUE);
@@ -652,118 +1659,176 @@ void purple_log_init(void)
 
 	purple_prefs_add_string("/purple/logging/format", "html");
 
-	html_logger = purple_log_logger_new("html", _("HTML"), 11,
-									  NULL,
-									  html_logger_write,
-									  html_logger_finalize,
-									  html_logger_list,
-									  html_logger_read,
-									  purple_log_common_sizer,
-									  html_logger_total_size,
-									  html_logger_list_syslog,
-									  NULL,
-									  purple_log_common_deleter,
-									  purple_log_common_is_deletable);
+	G_LOCK(html_logger);
+	html_logger = purple_log_logger_new("html", _("HTML"), 21,
+		NULL,
+		html_logger_write,
+		html_logger_finalize,
+		html_logger_list,
+		html_logger_read,
+		purple_log_common_sizer,
+		html_logger_total_size,
+		html_logger_list_syslog,
+		NULL,
+		purple_log_common_deleter,
+		purple_log_common_is_deletable,
+		NULL,
+		html_logger_write_async,
+		NULL,
+		html_logger_list_async,
+		html_logger_read_async,
+		purple_log_common_sizer_async,
+		html_logger_total_size_async,
+		html_logger_list_syslog_async,
+		NULL,
+		purple_log_common_deleter_async);
 	purple_log_logger_add(html_logger);
+	G_UNLOCK(html_logger);
 
-	txt_logger = purple_log_logger_new("txt", _("Plain text"), 11,
-									 NULL,
-									 txt_logger_write,
-									 txt_logger_finalize,
-									 txt_logger_list,
-									 txt_logger_read,
-									 purple_log_common_sizer,
-									 txt_logger_total_size,
-									 txt_logger_list_syslog,
-									 NULL,
-									 purple_log_common_deleter,
-									 purple_log_common_is_deletable);
+	G_LOCK(txt_logger);
+	txt_logger = purple_log_logger_new("txt", _("Plain text"), 21,
+		NULL,
+		txt_logger_write,
+		txt_logger_finalize,
+		txt_logger_list,
+		txt_logger_read,
+		purple_log_common_sizer,
+		txt_logger_total_size,
+		txt_logger_list_syslog,
+		NULL,
+		purple_log_common_deleter,
+		purple_log_common_is_deletable,
+		NULL,
+		txt_logger_write_async,
+		NULL,
+		txt_logger_list_async,
+		txt_logger_read_async,
+		purple_log_common_sizer_async,
+		txt_logger_total_size_async,
+		txt_logger_list_syslog_async,
+		NULL,
+		purple_log_common_deleter_async);
 	purple_log_logger_add(txt_logger);
+	G_UNLOCK(txt_logger);
 
+	G_LOCK(old_logger);
 	old_logger = purple_log_logger_new("old", _("Old flat format"), 9,
-									 NULL,
-									 NULL,
-									 old_logger_finalize,
-									 old_logger_list,
-									 old_logger_read,
-									 old_logger_size,
-									 old_logger_total_size,
-									 NULL,
-									 old_logger_get_log_sets);
+		NULL,
+		NULL,
+		old_logger_finalize,
+		old_logger_list,
+		old_logger_read,
+		old_logger_size,
+		old_logger_total_size,
+		NULL,
+		old_logger_get_log_sets);
 	purple_log_logger_add(old_logger);
+	G_UNLOCK(old_logger);
 
 	purple_signal_register(handle, "log-timestamp",
 #if SIZEOF_TIME_T == 4
-	                     purple_marshal_POINTER__POINTER_INT_BOOLEAN,
+		purple_marshal_POINTER__POINTER_INT_BOOLEAN,
 #elif SIZEOF_TIME_T == 8
-			     purple_marshal_POINTER__POINTER_INT64_BOOLEAN,
+		purple_marshal_POINTER__POINTER_INT64_BOOLEAN,
 #else
 #error Unknown size of time_t
 #endif
-	                     purple_value_new(PURPLE_TYPE_STRING), 3,
-	                     purple_value_new(PURPLE_TYPE_SUBTYPE,
-	                                    PURPLE_SUBTYPE_LOG),
+		purple_value_new(PURPLE_TYPE_STRING), 3,
+		purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_LOG),
 #if SIZEOF_TIME_T == 4
-	                     purple_value_new(PURPLE_TYPE_INT),
+		purple_value_new(PURPLE_TYPE_INT),
 #elif SIZEOF_TIME_T == 8
-	                     purple_value_new(PURPLE_TYPE_INT64),
+		purple_value_new(PURPLE_TYPE_INT64),
 #else
 # error Unknown size of time_t
 #endif
-	                     purple_value_new(PURPLE_TYPE_BOOLEAN));
+		purple_value_new(PURPLE_TYPE_BOOLEAN));
 
-	purple_prefs_connect_callback(NULL, "/purple/logging/format",
-							    logger_pref_cb, NULL);
+	purple_prefs_connect_callback(NULL, "/purple/logging/format", logger_pref_cb, NULL);
 	purple_prefs_trigger_callback("/purple/logging/format");
 
-	logsize_users = g_hash_table_new_full((GHashFunc)_purple_logsize_user_hash,
-			(GEqualFunc)_purple_logsize_user_equal,
-			(GDestroyNotify)_purple_logsize_user_free_key, NULL);
-	logsize_users_decayed = g_hash_table_new_full((GHashFunc)_purple_logsize_user_hash,
-				(GEqualFunc)_purple_logsize_user_equal,
-				(GDestroyNotify)_purple_logsize_user_free_key, NULL);
+	G_LOCK(logsize_users);
+	logsize_users = g_hash_table_new_full((GHashFunc) _purple_logsize_user_hash,
+		(GEqualFunc) _purple_logsize_user_equal,
+		(GDestroyNotify) _purple_logsize_user_free_key, NULL);
+	G_UNLOCK(logsize_users);
+
+	G_LOCK(logsize_users_decayed);
+	logsize_users_decayed = g_hash_table_new_full((GHashFunc) _purple_logsize_user_hash,
+		(GEqualFunc) _purple_logsize_user_equal,
+		(GDestroyNotify) _purple_logsize_user_free_key, NULL);
+	G_UNLOCK(logsize_users_decayed);
+
+	G_LOCK(log_ref_table);
+	log_ref_table = g_hash_table_new(NULL, NULL);
+	G_UNLOCK(log_ref_table);
 }
 
+void *
+purple_log_get_handle(void)
+{
+	static gint handle;
+
+	return &handle;
+}
+
 void
 purple_log_uninit(void)
 {
 	purple_signals_unregister_by_instance(purple_log_get_handle());
 
+	G_LOCK(html_logger);
 	purple_log_logger_remove(html_logger);
 	purple_log_logger_free(html_logger);
 	html_logger = NULL;
+	G_UNLOCK(html_logger);
 
+	G_LOCK(txt_logger);
 	purple_log_logger_remove(txt_logger);
 	purple_log_logger_free(txt_logger);
 	txt_logger = NULL;
+	G_UNLOCK(txt_logger);
 
+	G_LOCK(old_logger);
 	purple_log_logger_remove(old_logger);
 	purple_log_logger_free(old_logger);
 	old_logger = NULL;
+	G_UNLOCK(old_logger);
 
+	G_LOCK(logsize_users);
 	g_hash_table_destroy(logsize_users);
+	G_UNLOCK(logsize_users);
+
+	G_LOCK(logsize_users_decayed);
 	g_hash_table_destroy(logsize_users_decayed);
+	G_UNLOCK(logsize_users_decayed);
+
+	G_LOCK(log_ref_table);
+	g_hash_table_destroy(log_ref_table);
+	G_UNLOCK(log_ref_table);
 }
 
 /****************************************************************************
  * LOGGERS ******************************************************************
  ****************************************************************************/
 
-static char *log_get_timestamp(PurpleLog *log, time_t when)
+static gchar *
+log_get_timestamp(PurpleLog *log, time_t when)
 {
 	gboolean show_date;
-	char *date;
+	gchar *date;
 	struct tm tm;
 
-	show_date = (log->type == PURPLE_LOG_SYSTEM) || (time(NULL) > when + 20*60);
+	show_date = log->type == PURPLE_LOG_SYSTEM || time(NULL) > when + 20 * 60;
 
-	date = purple_signal_emit_return_1(purple_log_get_handle(),
-	                          "log-timestamp",
-	                          log, when, show_date);
+	date = purple_signal_emit_return_1(purple_log_get_handle(), "log-timestamp",
+		log, when, show_date);
+
 	if (date != NULL)
 		return date;
 
 	tm = *(localtime(&when));
+
 	if (show_date)
 		return g_strdup(purple_date_format_long(&tm));
 	else
@@ -772,20 +1837,16 @@ static char *log_get_timestamp(PurpleLog
 
 /* NOTE: This can return msg (which you may or may not want to g_free())
  * NOTE: or a newly allocated string which you MUST g_free(). */
-static char *
-convert_image_tags(const PurpleLog *log, const char *msg)
+static gchar *
+convert_image_tags(const PurpleLog *log, const gchar *msg)
 {
-	const char *tmp;
-	const char *start;
-	const char *end;
+	GString *newmsg = NULL;
 	GData *attributes;
-	GString *newmsg = NULL;
+	const gchar *tmp = msg, *start, *end;
 
-	tmp = msg;
-
 	while (purple_markup_find_tag("img", tmp, &start, &end, &attributes)) {
-		int imgid = 0;
-		char *idstr = NULL;
+		gchar *idstr = NULL;
+		gint imgid = 0;
 
 		if (newmsg == NULL)
 			newmsg = g_string_new("");
@@ -797,56 +1858,45 @@ convert_image_tags(const PurpleLog *log,
 		if ((idstr = g_datalist_get_data(&attributes, "id")) != NULL)
 			imgid = atoi(idstr);
 
-		if (imgid != 0)
-		{
-			FILE *image_file;
-			char *dir;
-			PurpleStoredImage *image;
+		if (imgid != 0) {
+			PurpleStoredImage *image = purple_imgstore_find_by_id(imgid);
+			gchar *new_filename = NULL, *path = NULL, *dir;
 			gconstpointer image_data;
-			char *new_filename = NULL;
-			char *path = NULL;
 			size_t image_byte_count;
 
-			image = purple_imgstore_find_by_id(imgid);
-			if (image == NULL)
-			{
+			if (image == NULL) {
 				/* This should never happen. */
 				/* This *does* happen for failed Direct-IMs -DAA */
 				g_string_free(newmsg, TRUE);
-				g_return_val_if_reached((char *)msg);
+				g_return_val_if_reached((gchar *) msg);
 			}
 
-			image_data       = purple_imgstore_get_data(image);
+			image_data = purple_imgstore_get_data(image);
 			image_byte_count = purple_imgstore_get_size(image);
-			dir              = purple_log_get_log_dir(log->type, log->name, log->account);
-			new_filename     = purple_util_get_image_filename(image_data, image_byte_count);
+			dir = purple_log_get_log_dir(log->type, log->name, log->account);
+			new_filename = purple_util_get_image_filename(image_data, image_byte_count);
 
 			path = g_build_filename(dir, new_filename, NULL);
 
 			/* Only save unique files. */
-			if (!g_file_test(path, G_FILE_TEST_EXISTS))
-			{
-				if ((image_file = g_fopen(path, "wb")) != NULL)
-				{
-					if (!fwrite(image_data, image_byte_count, 1, image_file))
-					{
+			if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
+				FILE *image_file = g_fopen(path, "wb");
+
+				if (image_file != NULL) {
+					if (!fwrite(image_data, image_byte_count, 1, image_file)) {
 						purple_debug_error("log", "Error writing %s: %s\n",
-						                   path, g_strerror(errno));
+							path, g_strerror(errno));
 						fclose(image_file);
 
 						/* Attempt to not leave half-written files around. */
 						unlink(path);
-					}
-					else
-					{
+					} else {
 						purple_debug_info("log", "Wrote image file: %s\n", path);
 						fclose(image_file);
 					}
-				}
-				else
-				{
+				} else {
 					purple_debug_error("log", "Unable to create file %s: %s\n",
-					                   path, g_strerror(errno));
+						path, g_strerror(errno));
 				}
 			}
 
@@ -860,10 +1910,9 @@ convert_image_tags(const PurpleLog *log,
 		tmp = end + 1;
 	}
 
-	if (newmsg == NULL)
-	{
+	if (newmsg == NULL) {
 		/* No images were found to change. */
-		return (char *)msg;
+		return (gchar *) msg;
 	}
 
 	/* Append any remaining message data */
@@ -872,21 +1921,20 @@ convert_image_tags(const PurpleLog *log,
 	return g_string_free(newmsg, FALSE);
 }
 
-void purple_log_common_writer(PurpleLog *log, const char *ext)
+void
+purple_log_common_writer(PurpleLog *log, const gchar *ext)
 {
-	PurpleLogCommonLoggerData *data = log->logger_data;
+	g_return_if_fail(log != NULL);
 
-	if (data == NULL)
-	{
-		/* This log is new */
-		char *dir;
+	if (log->logger_data == NULL) {
+		PurpleLogCommonLoggerData *data = log->logger_data;
 		struct tm *tm;
-		const char *tz;
-		const char *date;
-		char *filename;
-		char *path;
+		const gchar *tz, *date;
+		gchar *dir, *filename, *path;
 
+		/* This log is new */
 		dir = purple_log_get_log_dir(log->type, log->name, log->account);
+
 		if (dir == NULL)
 			return;
 
@@ -896,7 +1944,7 @@ void purple_log_common_writer(PurpleLog 
 		tz = purple_escape_filename(purple_utf8_strftime("%Z", tm));
 		date = purple_utf8_strftime("%Y-%m-%d.%H%M%S%z", tm);
 
-		filename = g_strdup_printf("%s%s%s", date, tz, ext ? ext : "");
+		filename = g_strdup_printf("%s%s%s", date, tz, ext != NULL ? ext : "");
 
 		path = g_build_filename(dir, filename, NULL);
 		g_free(dir);
@@ -905,53 +1953,253 @@ void purple_log_common_writer(PurpleLog 
 		log->logger_data = data = g_slice_new0(PurpleLogCommonLoggerData);
 
 		data->file = g_fopen(path, "a");
-		if (data->file == NULL)
-		{
-			purple_debug(PURPLE_DEBUG_ERROR, "log",
-					"Could not create log file %s\n", path);
 
+		if (data->file == NULL) {
+			purple_debug_error("log", "Could not create log file %s\n", path);
+
 			if (log->conv != NULL)
 				purple_conversation_write(log->conv, NULL, _("Logging of this conversation failed."),
-										PURPLE_MESSAGE_ERROR, time(NULL));
+					PURPLE_MESSAGE_ERROR, time(NULL));
 
 			g_free(path);
+
 			return;
 		}
-		g_free(path);
+
+		data->path = path;
 	}
 }
 
-GList *purple_log_common_lister(PurpleLogType type, const char *name, PurpleAccount *account, const char *ext, PurpleLogLogger *logger)
+static void
+writer_thread(GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable)
 {
+	_purple_logger_writer_callback_data *callback_data =
+		g_simple_async_result_get_op_res_gpointer(simple);
+	PurpleLog *log = callback_data->log;
+	PurpleLogCommonLoggerData *data = log->logger_data;
+
+	if (data == NULL) {
+		struct tm *tm;
+		const gchar *tz, *date;
+		gchar *dir, *filename, *path, *ext = callback_data->ext;
+
+		/* This log is new */
+		dir = purple_log_get_log_dir(log->type, log->name, log->account);
+
+		if (dir == NULL) {
+			g_simple_async_result_set_error(simple, G_IO_ERROR, G_IO_ERROR_FAILED,
+				_("Unable to get log directory"));
+
+			return;
+		}
+
+		if (purple_build_dir(dir, S_IRUSR | S_IWUSR | S_IXUSR) < 0) {
+			g_simple_async_result_set_error(simple, G_FILE_ERROR, g_file_error_from_errno(errno),
+				"%s", g_strerror(errno));
+
+			return;
+		}
+
+		tm = localtime(&log->time);
+		tz = purple_escape_filename(purple_utf8_strftime("%Z", tm));
+		date = purple_utf8_strftime("%Y-%m-%d.%H%M%S%z", tm);
+
+		filename = g_strdup_printf("%s%s%s", date, tz, ext != NULL ? ext : "");
+		path = g_build_filename(dir, filename, NULL);
+
+		g_free(dir);
+		g_free(filename);
+
+		log->logger_data = data = g_slice_new0(PurpleLogCommonLoggerData);
+		data->file = g_fopen(path, "a");
+
+		if (data->file == NULL) {
+			g_simple_async_result_set_error(simple, G_FILE_ERROR,
+				g_file_error_from_errno(errno), "%s", g_strerror(errno));
+
+			if (log->conv != NULL)
+				purple_conversation_write(log->conv, NULL,
+					_("Logging of this conversation failed."),
+					PURPLE_MESSAGE_ERROR, time(NULL));
+
+			g_free(path);
+
+			return;
+		}
+
+		data->path = path;
+	}
+
+	g_simple_async_result_set_op_res_gboolean(simple, TRUE);
+}
+
+void
+purple_log_common_writer_async(PurpleLog *log, const gchar *ext, gint io_priority,
+	GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata)
+{
+	_purple_logger_writer_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+
+	g_return_if_fail(log != NULL);
+
+	callback_data = g_new0(_purple_logger_writer_callback_data, 1);
+	callback_data->log = log;
+	callback_data->ext = g_strdup(ext);
+
+	simple = g_simple_async_result_new(NULL, cb, userdata, purple_log_common_writer_async);
+
+	g_simple_async_result_set_op_res_gpointer(simple, callback_data, purple_logger_writer_callback_data_free);
+	g_simple_async_result_run_in_thread(simple, writer_thread, io_priority, cancellable);
+
+	g_object_unref(simple);
+}
+
+gboolean
+purple_log_common_writer_finish(PurpleLog *log, GAsyncResult *res, GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = G_SIMPLE_ASYNC_RESULT(res);
+
+	if (g_simple_async_result_propagate_error(simple, error))
+		return FALSE;
+
+	g_return_val_if_fail(g_simple_async_result_get_source_tag(simple) == purple_log_common_writer_async, FALSE);
+
+	return g_simple_async_result_get_op_res_gboolean(simple);
+}
+
+GList *
+purple_log_common_lister(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	const gchar *ext, PurpleLogLogger *logger)
+{
+	PurpleLog *log;
 	GDir *dir;
 	GList *list = NULL;
-	const char *filename;
-	char *path;
+	const gchar *filename;
+	gchar *path;
+	PurpleLogCommonLoggerData *data;
+	struct tm tm;
 
-	if(!account)
-		return NULL;
+	g_return_val_if_fail(name != NULL, NULL);
+	g_return_val_if_fail(account != NULL, NULL);
+	g_return_val_if_fail(ext != NULL, NULL);
+	g_return_val_if_fail(logger != NULL, NULL);
 
 	path = purple_log_get_log_dir(type, name, account);
+
 	if (path == NULL)
 		return NULL;
 
-	if (!(dir = g_dir_open(path, 0, NULL)))
-	{
+	dir = g_dir_open(path, 0, NULL);
+
+	if (dir == NULL) {
 		g_free(path);
+
 		return NULL;
 	}
 
-	while ((filename = g_dir_read_name(dir)))
-	{
+	while ((filename = g_dir_read_name(dir)) != NULL) {
 		if (purple_str_has_suffix(filename, ext) &&
-		    strlen(filename) >= (17 + strlen(ext)))
-		{
-			PurpleLog *log;
-			PurpleLogCommonLoggerData *data;
-			struct tm tm;
+		    strlen(filename) >= (17 + strlen(ext))) {
 #if defined (HAVE_TM_GMTOFF) && defined (HAVE_STRUCT_TM_TM_ZONE)
 			long tz_off;
-			const char *rest, *end;
+			const gchar *rest, *end;
+			time_t stamp;
+
+			stamp = purple_str_to_time(purple_unescape_filename(filename), FALSE, &tm, &tz_off, &rest);
+
+			/* As zero is a valid offset, PURPLE_NO_TZ_OFF means no offset was
+			 * provided. See util.h. Yes, it's kinda ugly. */
+			if (tz_off != PURPLE_NO_TZ_OFF)
+				tm.tm_gmtoff = tz_off - tm.tm_gmtoff;
+
+			if (stamp == 0 || rest == NULL || (end = strchr(rest, '.')) == NULL || strchr(rest, ' ') != NULL) {
+				log = purple_log_new(type, name, account, NULL, stamp, NULL);
+			} else {
+				gchar *tmp = g_strndup(rest, end - rest);
+				tm.tm_zone = tmp;
+				log = purple_log_new(type, name, account, NULL, stamp, &tm);
+				g_free(tmp);
+			}
+#else
+			time_t stamp = purple_str_to_time(filename, FALSE, &tm, NULL, NULL);
+
+			log = purple_log_new(type, name, account, NULL, stamp, (stamp != 0) ?  &tm : NULL);
+#endif
+
+			log->logger = logger;
+			log->logger_data = data = g_slice_new0(PurpleLogCommonLoggerData);
+
+			data->path = g_build_filename(path, filename, NULL);
+			list = g_list_prepend(list, log);
+		}
+	}
+
+	g_dir_close(dir);
+	g_free(path);
+
+	return list;
+}
+
+static void
+common_lister_thread(GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable)
+{
+	_purple_logger_lister_callback_data *callback_data =
+		g_simple_async_result_get_op_res_gpointer(simple);
+	PurpleAccount *account = callback_data->account;
+	PurpleLog *log;
+	PurpleLogCommonLoggerData *data;
+	PurpleLogLogger *logger = callback_data->logger;
+	PurpleLogType type = callback_data->type;
+	GError *err = NULL;
+	GList *list = NULL;
+	GDir *dir;
+	const gchar *name = callback_data->name, *ext = callback_data->ext, *filename;
+	gchar *path;
+	struct tm tm;
+
+	path = purple_log_get_log_dir(type, name, account);
+
+	if (path == NULL) {
+		g_simple_async_result_set_error(simple, G_IO_ERROR, G_IO_ERROR_FAILED,
+			_("Unable to get log directory"));
+
+		return;
+	}
+
+	dir = g_dir_open(path, 0, &err);
+
+	if (dir == NULL) {
+		/* Should we indicate if the directory was just empty, as we do with total_sizer?
+		 * or just let the caller figure that sort of thing out? Feels weird to be inconsistent...
+		 */
+		g_simple_async_result_set_from_error(simple, err);
+
+		g_free(path);
+		g_clear_error(&err);
+
+		return;
+	}
+
+	g_clear_error(&err);
+
+	while ((filename = g_dir_read_name(dir)) != NULL) {
+		if (g_cancellable_set_error_if_cancelled(cancellable, &err)) {
+			g_simple_async_result_set_from_error(simple, err);
+
+			g_dir_close(dir);
+			g_free(path);
+			g_clear_error(&err);
+
+			return;
+		}
+
+		if (purple_str_has_suffix(filename, ext) &&
+		    strlen(filename) >= (17 + strlen(ext))) {
+#if defined (HAVE_TM_GMTOFF) && defined (HAVE_STRUCT_TM_TM_ZONE)
+			long tz_off;
+			const gchar *rest, *end;
 			time_t stamp = purple_str_to_time(purple_unescape_filename(filename), FALSE, &tm, &tz_off, &rest);
 
 			/* As zero is a valid offset, PURPLE_NO_TZ_OFF means no offset was
@@ -959,13 +2207,10 @@ GList *purple_log_common_lister(PurpleLo
 			if (tz_off != PURPLE_NO_TZ_OFF)
 				tm.tm_gmtoff = tz_off - tm.tm_gmtoff;
 
-			if (stamp == 0 || rest == NULL || (end = strchr(rest, '.')) == NULL || strchr(rest, ' ') != NULL)
-			{
+			if (stamp == 0 || rest == NULL || (end = strchr(rest, '.')) == NULL || strchr(rest, ' ') != NULL) {
 				log = purple_log_new(type, name, account, NULL, stamp, NULL);
-			}
-			else
-			{
-				char *tmp = g_strndup(rest, end - rest);
+			} else {
+				gchar *tmp = g_strndup(rest, end - rest);
 				tm.tm_zone = tmp;
 				log = purple_log_new(type, name, account, NULL, stamp, &tm);
 				g_free(tmp);
@@ -983,58 +2228,283 @@ GList *purple_log_common_lister(PurpleLo
 			list = g_list_prepend(list, log);
 		}
 	}
+
 	g_dir_close(dir);
 	g_free(path);
-	return list;
+
+	g_simple_async_result_set_op_res_gpointer(simple, list, NULL);
 }
 
-int purple_log_common_total_sizer(PurpleLogType type, const char *name, PurpleAccount *account, const char *ext)
+void
+purple_log_common_lister_async(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	const gchar *ext, PurpleLogLogger *logger, gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata)
 {
+	_purple_logger_lister_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+
+	g_return_if_fail(name != NULL);
+	g_return_if_fail(account != NULL);
+	g_return_if_fail(ext != NULL);
+	g_return_if_fail(logger != NULL);
+
+	callback_data = g_new0(_purple_logger_lister_callback_data, 1);
+	callback_data->type = type;
+	callback_data->name = g_strdup(name);
+	callback_data->account = account;
+	callback_data->ext = g_strdup(ext);
+	callback_data->logger = logger;
+
+	simple = g_simple_async_result_new(NULL, cb, userdata, purple_log_common_lister_async);
+
+	g_simple_async_result_set_op_res_gpointer(simple, callback_data,
+		purple_logger_lister_callback_data_free);
+	g_simple_async_result_run_in_thread(simple, common_lister_thread, io_priority,
+		cancellable);
+
+	g_object_unref(simple);
+}
+
+GList *
+purple_log_common_lister_finish(GAsyncResult *res, GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = G_SIMPLE_ASYNC_RESULT(res);
+
+	if (g_simple_async_result_propagate_error(simple, error))
+		return NULL;
+
+	g_return_val_if_fail(g_simple_async_result_get_source_tag(simple) == purple_log_common_lister_async, NULL);
+
+	return g_simple_async_result_get_op_res_gpointer(simple);
+}
+
+// TODO: Rather than calling this multiple times with different extensions,
+// TODO: could we somehow store up all the extensions and do the loop just
+// TODO: once?  This may be possible with the non-blocking stuff...
+gint
+purple_log_common_total_sizer(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	const gchar *ext)
+{
+	_purple_logsize_user *lu;
 	GDir *dir;
-	int size = 0;
-	const char *filename;
-	char *path;
+	gint size;
+	const gchar *filename;
+	gchar *tmp, *path;
+	struct stat st;
+	gpointer ptrsize;
 
-	if(!account)
-		return 0;
+	g_return_val_if_fail(name != NULL, 0);
+	g_return_val_if_fail(account != NULL, 0);
+	g_return_val_if_fail(ext != NULL, 0);
 
 	path = purple_log_get_log_dir(type, name, account);
+
 	if (path == NULL)
 		return 0;
 
-	if (!(dir = g_dir_open(path, 0, NULL)))
-	{
+	if (!(dir = g_dir_open(path, 0, NULL))){
 		g_free(path);
 		return 0;
 	}
 
-	while ((filename = g_dir_read_name(dir)))
-	{
+	size = 0;
+
+	while ((filename = g_dir_read_name(dir))) {
 		if (purple_str_has_suffix(filename, ext) &&
-		    strlen(filename) >= (17 + strlen(ext)))
-		{
-			char *tmp = g_build_filename(path, filename, NULL);
-			struct stat st;
-			if (g_stat(tmp, &st))
-			{
+		    strlen(filename) >= (17 + strlen(ext))) {
+			tmp = g_build_filename(path, filename, NULL);
+
+			if (g_stat(tmp, &st)) {
 				purple_debug_error("log", "Error stating log file: %s\n", tmp);
 				g_free(tmp);
+
 				continue;
 			}
+
 			g_free(tmp);
 			size += st.st_size;
 		}
 	}
+
 	g_dir_close(dir);
 	g_free(path);
+
+	G_LOCK(logsize_users);
+	lu = g_new(_purple_logsize_user, 1);
+	lu->name = g_strdup(purple_normalize(account, name));
+	lu->account = account; //g_object_ref?
+
+	if (g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize))
+		size += GPOINTER_TO_INT(ptrsize);
+
+	g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(size));
+	G_UNLOCK(logsize_users);
+
 	return size;
 }
 
-int purple_log_common_sizer(PurpleLog *log)
+static void
+total_sizer_thread(GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable)
 {
+	_purple_logger_total_size_callback_data *callback_data =
+		g_simple_async_result_get_op_res_gpointer(simple);
+	_purple_logsize_user *lu;
+	PurpleAccount *account = callback_data->account;
+	PurpleLogType type = callback_data->type;
+	GDir *dir;
+	GError *err = NULL;
+	gchar *tmp, *path, *name = callback_data->name, *ext = callback_data->ext;
+	const gchar *filename;
 	struct stat st;
-	PurpleLogCommonLoggerData *data = log->logger_data;
+	gssize size = 0, total;
+	gpointer ptrsize;
 
+	if(account == NULL) {
+		g_simple_async_result_set_error(simple, G_IO_ERROR, G_IO_ERROR_FAILED,
+			_("Account is NULL"));
+
+		return;
+	}
+
+	path = purple_log_get_log_dir(type, name, account);
+
+	if (path == NULL) {
+		g_simple_async_result_set_error(simple, G_IO_ERROR, G_IO_ERROR_FAILED,
+			_("Unable to get log directory"));
+
+		return;
+	}
+
+	dir = g_dir_open(path, 0, &err);
+
+	if (dir == NULL){
+		/* If the directory doesn't exist, we just don't have logs for them */
+		if (err->code == G_FILE_ERROR_NOENT) {
+			G_LOCK(logsize_users);
+			lu = g_new(_purple_logsize_user, 1);
+			lu->name = g_strdup(purple_normalize(account, name));
+			lu->account = account; //g_object_ref?
+
+			if (!g_hash_table_lookup_extended(logsize_users, lu, NULL, NULL))
+				g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(size));
+
+			G_UNLOCK(logsize_users);
+
+			g_simple_async_result_set_op_res_gssize(simple, size);
+		} else
+			g_simple_async_result_set_from_error(simple, err);
+
+		g_free(path);
+		g_clear_error(&err);
+
+		return;
+	}
+
+	g_clear_error(&err);
+
+	while ((filename = g_dir_read_name(dir)) != NULL) {
+		if (g_cancellable_set_error_if_cancelled(cancellable, &err)) {
+			g_simple_async_result_set_from_error(simple, err);
+
+			g_dir_close(dir);
+			g_free(path);
+			g_clear_error(&err);
+
+			return;
+		}
+
+		g_clear_error(&err);
+
+		if (purple_str_has_suffix(filename, ext) &&
+		    strlen(filename) >= 17 + strlen(ext)) {
+			tmp = g_build_filename(path, filename, NULL);
+
+			if (g_stat(tmp, &st)) {
+				purple_debug_error("log", "Error stating log file: %s\n", tmp);
+				g_free(tmp);
+
+				continue;
+			}
+
+			g_free(tmp);
+			size += st.st_size;
+		}
+	}
+
+
+	G_LOCK(logsize_users);
+	lu = g_new(_purple_logsize_user, 1);
+	lu->name = g_strdup(purple_normalize(account, name));
+	lu->account = account; //g_object_ref?
+
+	total = size;
+
+	if (g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize))
+		total += GPOINTER_TO_INT(ptrsize);
+
+	g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(total));
+	G_UNLOCK(logsize_users);
+
+	g_dir_close(dir);
+	g_free(path);
+
+	g_simple_async_result_set_op_res_gssize(simple, size);
+}
+
+void
+purple_log_common_total_sizer_async(PurpleLogType type, const gchar *name,
+	PurpleAccount *account, const gchar *ext, gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata)
+{
+	_purple_logger_total_size_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+
+	g_return_if_fail(name != NULL);
+	g_return_if_fail(account != NULL);
+	g_return_if_fail(ext != NULL);
+
+	callback_data = g_new0(_purple_logger_total_size_callback_data, 1);
+	callback_data->type = type;
+	callback_data->name = g_strdup(name);
+	callback_data->account = account;
+	callback_data->ext = g_strdup(ext);
+
+	simple = g_simple_async_result_new(NULL, cb, userdata, purple_log_common_total_sizer_async);
+
+	g_simple_async_result_set_op_res_gpointer(simple, callback_data,
+		purple_logger_total_size_callback_data_free);
+	g_simple_async_result_run_in_thread(simple, total_sizer_thread, io_priority, cancellable);
+
+	g_object_unref(simple);
+}
+
+gssize
+purple_log_common_total_sizer_finish(GAsyncResult *res, GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = G_SIMPLE_ASYNC_RESULT(res);
+
+	if (g_simple_async_result_propagate_error(simple, error))
+		return -1;
+
+	g_return_val_if_fail(g_simple_async_result_get_source_tag(simple) == purple_log_common_total_sizer_async, -1);
+
+	return g_simple_async_result_get_op_res_gssize(simple);
+}
+
+gint
+purple_log_common_sizer(PurpleLog *log)
+{
+	struct stat st;
+	PurpleLogCommonLoggerData *data;
+
+	g_return_val_if_fail(log != NULL, 0);
+
+	data = log->logger_data;
+
 	g_return_val_if_fail(data != NULL, 0);
 
 	if (!data->path || g_stat(data->path, &st))
@@ -1043,28 +2513,100 @@ int purple_log_common_sizer(PurpleLog *l
 	return st.st_size;
 }
 
+static void
+sizer_thread(GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable)
+{
+	_purple_logger_sizer_callback_data *callback_data =
+		g_simple_async_result_get_op_res_gpointer(simple);
+	PurpleLogCommonLoggerData *data = callback_data->log->logger_data;
+	struct stat st;
+
+	if (data == NULL || data->path == NULL) {
+		g_simple_async_result_set_error(simple, G_IO_ERROR, G_IO_ERROR_FAILED,
+			_("Unable to get log path"));
+
+		return;
+	}
+
+	if (g_stat(data->path, &st)) {
+		g_simple_async_result_set_error(simple, G_IO_ERROR, G_IO_ERROR_FAILED,
+			_("Error stating log file"));
+
+		return;
+	}
+
+	g_simple_async_result_set_op_res_gssize(simple, st.st_size);
+}
+
+void
+purple_log_common_sizer_async(PurpleLog *log, gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata)
+{
+	_purple_logger_sizer_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+
+	g_return_if_fail(log != NULL);
+
+	callback_data = g_new0(_purple_logger_sizer_callback_data, 1);
+	callback_data->log = log;
+
+	simple = g_simple_async_result_new(NULL, cb, userdata, purple_log_common_sizer_async);
+
+	g_simple_async_result_set_op_res_gpointer(simple, callback_data, g_free);
+	g_simple_async_result_run_in_thread(simple, sizer_thread, io_priority,
+		cancellable);
+
+	g_object_unref(simple);
+}
+
+gssize
+purple_log_common_sizer_finish(PurpleLog *log, GAsyncResult *res, GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = G_SIMPLE_ASYNC_RESULT(res);
+
+	if (g_simple_async_result_propagate_error(simple, error))
+		return -1;
+
+	g_return_val_if_fail(g_simple_async_result_get_source_tag(simple) == purple_log_common_sizer_async, -1);
+
+	return g_simple_async_result_get_op_res_gssize(simple);
+}
+
 /* This will build log sets for all loggers that use the common logger
  * functions because they use the same directory structure. */
-static void log_get_log_sets_common(GHashTable *sets)
+static gboolean
+log_get_log_sets_common(GHashTable *sets, GMutex *mutex, PurpleLogSetCallback set_cb, GError **error)
 {
-	gchar *log_path = g_build_filename(purple_user_dir(), "logs", NULL);
-	GDir *log_dir = g_dir_open(log_path, 0, NULL);
-	const gchar *protocol;
+	PurpleAccount *account;
+	PurplePlugin *prpl;
+	PurplePluginProtocolInfo *prpl_info;
+	GDir *log_dir, *protocol_dir, *username_dir;
+	GError *err = NULL;
+	GList *accounts, *account_iter;
+	const gchar *protocol, *prpl_protocol, *username, *username_unescaped;
+	gchar *log_path, *protocol_path, *protocol_unescaped, *username_path, *name, *tmp;
+	guint len;
 
+	log_path = g_build_filename(purple_user_dir(), "logs", NULL);
+	log_dir = g_dir_open(log_path, 0, &err);
+
 	if (log_dir == NULL) {
+		if (error != NULL)
+			*error = err;
+
 		g_free(log_path);
-		return;
+		return FALSE;
 	}
 
+	g_clear_error(&err);
+
 	while ((protocol = g_dir_read_name(log_dir)) != NULL) {
-		gchar *protocol_path = g_build_filename(log_path, protocol, NULL);
-		GDir *protocol_dir;
-		const gchar *username;
-		gchar *protocol_unescaped;
-		GList *account_iter;
-		GList *accounts = NULL;
+		protocol_path = g_build_filename(log_path, protocol, NULL);
+		protocol_dir = g_dir_open(protocol_path, 0, NULL);
 
-		if ((protocol_dir = g_dir_open(protocol_path, 0, NULL)) == NULL) {
+		if (protocol_dir == NULL) {
 			g_free(protocol_path);
 			continue;
 		}
@@ -1072,28 +2614,27 @@ static void log_get_log_sets_common(GHas
 		/* Using g_strdup() to cover the one-in-a-million chance that a
 		 * prpl's list_icon function uses purple_unescape_filename(). */
 		protocol_unescaped = g_strdup(purple_unescape_filename(protocol));
+		accounts = NULL;
 
 		/* Find all the accounts for protocol. */
-		for (account_iter = purple_accounts_get_all() ; account_iter != NULL ; account_iter = account_iter->next) {
-			PurplePlugin *prpl;
-			PurplePluginProtocolInfo *prpl_info;
+		for (account_iter = purple_accounts_get_all(); account_iter != NULL; account_iter = g_list_next(account_iter)) {
+			prpl = purple_find_prpl(purple_account_get_protocol_id((PurpleAccount *) account_iter->data));
 
-			prpl = purple_find_prpl(purple_account_get_protocol_id((PurpleAccount *)account_iter->data));
 			if (!prpl)
 				continue;
+
 			prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
+			prpl_protocol = prpl_info->list_icon((PurpleAccount *) account_iter->data, NULL);
 
-			if (purple_strequal(protocol_unescaped, prpl_info->list_icon((PurpleAccount *)account_iter->data, NULL)))
+			if (purple_strequal(protocol_unescaped, prpl_protocol))
 				accounts = g_list_prepend(accounts, account_iter->data);
 		}
+
 		g_free(protocol_unescaped);
 
 		while ((username = g_dir_read_name(protocol_dir)) != NULL) {
-			gchar *username_path = g_build_filename(protocol_path, username, NULL);
-			GDir *username_dir;
-			const gchar *username_unescaped;
-			PurpleAccount *account = NULL;
-			gchar *name;
+			username_path = g_build_filename(protocol_path, username, NULL);
+			account = NULL;
 
 			if ((username_dir = g_dir_open(username_path, 0, NULL)) == NULL) {
 				g_free(username_path);
@@ -1102,20 +2643,18 @@ static void log_get_log_sets_common(GHas
 
 			/* Find the account for username in the list of accounts for protocol. */
 			username_unescaped = purple_unescape_filename(username);
-			for (account_iter = g_list_first(accounts) ; account_iter != NULL ; account_iter = account_iter->next) {
-				if (purple_strequal(((PurpleAccount *)account_iter->data)->username, username_unescaped)) {
+
+			for (account_iter = g_list_first(accounts); account_iter != NULL; account_iter = g_list_next(account_iter)) {
+				if (purple_strequal(((PurpleAccount *) account_iter->data)->username, username_unescaped)) {
 					account = account_iter->data;
 					break;
 				}
 			}
 
 			/* Don't worry about the cast, name will point to dynamically allocated memory shortly. */
-			while ((name = (gchar *)g_dir_read_name(username_dir)) != NULL) {
-				size_t len;
-				PurpleLogSet *set;
-
+			while ((name = (gchar *) g_dir_read_name(username_dir)) != NULL) {
 				/* IMPORTANT: Always initialize all members of PurpleLogSet */
-				set = g_slice_new(PurpleLogSet);
+				PurpleLogSet *set = g_slice_new(PurpleLogSet);
 
 				/* Unescape the filename. */
 				name = g_strdup(purple_unescape_filename(name));
@@ -1131,14 +2670,17 @@ static void log_get_log_sets_common(GHas
 
 				/* Check for .chat or .system at the end of the name to determine the type. */
 				if (len >= 7) {
-					gchar *tmp = &name[len - 7];
+					tmp = &name[len - 7];
+
 					if (purple_strequal(tmp, ".system")) {
 						set->type = PURPLE_LOG_SYSTEM;
 						*tmp = '\0';
 					}
 				}
+
 				if (len > 5) {
-					gchar *tmp = &name[len - 5];
+					tmp = &name[len - 5];
+
 					if (purple_strequal(tmp, ".chat")) {
 						set->type = PURPLE_LOG_CHAT;
 						*tmp = '\0';
@@ -1151,86 +2693,194 @@ static void log_get_log_sets_common(GHas
 				else
 					set->buddy = FALSE;
 
-				log_add_log_set_to_hash(sets, set);
+				if (mutex != NULL) {
+					g_mutex_lock(mutex);
+					set_cb(sets, set);
+					g_mutex_unlock(mutex);
+				} else
+					set_cb(sets, set);
 			}
+
 			g_free(username_path);
 			g_dir_close(username_dir);
 		}
+
 		g_free(protocol_path);
 		g_dir_close(protocol_dir);
 	}
+
 	g_free(log_path);
 	g_dir_close(log_dir);
+
+	return TRUE;
 }
 
-gboolean purple_log_common_deleter(PurpleLog *log)
+static void
+log_get_log_sets_common_thread(GSimpleAsyncResult *simple, GObject *object,
+	GCancellable *cancellable)
 {
+	_purple_logger_get_sets_common_callback_data *callback_data =
+		g_simple_async_result_get_op_res_gpointer(simple);
+	GError *err = NULL;
+	GHashTable *sets = callback_data->sets;
+
+	if (!log_get_log_sets_common(sets, callback_data->mutex,
+		callback_data->set_cb, &err))
+		g_simple_async_result_set_from_error(simple, err);
+	else
+		g_simple_async_result_set_op_res_gpointer(simple, sets,
+			(GDestroyNotify) g_hash_table_unref);
+
+	g_clear_error(&err);
+}
+
+static void
+log_get_log_sets_common_async(GHashTable *sets, GMutex *mutex, PurpleLogSetCallback set_cb,
+	gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata)
+{
+	_purple_logger_get_sets_common_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+
+	callback_data = g_new0(_purple_logger_get_sets_common_callback_data, 1);
+	callback_data->sets = g_hash_table_ref(sets);
+	callback_data->mutex = mutex;
+	callback_data->set_cb = set_cb;
+
+	simple = g_simple_async_result_new(NULL, cb, userdata, log_get_log_sets_common_async);
+
+	g_simple_async_result_set_op_res_gpointer(simple, callback_data, g_free);
+	g_simple_async_result_run_in_thread(simple, log_get_log_sets_common_thread, io_priority,
+		cancellable);
+
+	g_object_unref(simple);
+}
+
+gboolean
+purple_log_common_deleter(PurpleLog *log)
+{
 	PurpleLogCommonLoggerData *data;
-	int ret;
+	GFile *file;
+	GError *err;
+	gboolean result;
 
 	g_return_val_if_fail(log != NULL, FALSE);
 
 	data = log->logger_data;
-	if (data == NULL)
+
+	if (data == NULL || data->path == NULL)
 		return FALSE;
 
-	if (data->path == NULL)
-		return FALSE;
+	err = NULL;
+	file = g_file_new_for_path(data->path);
+	result = g_file_delete(file, NULL, &err);
 
-	ret = g_unlink(data->path);
-	if (ret == 0)
-		return TRUE;
-	else if (ret == -1)
-	{
-		purple_debug_error("log", "Failed to delete: %s - %s\n", data->path, g_strerror(errno));
+	if (!result && err->code != G_IO_ERROR_CANCELLED)
+		purple_debug_error("log", "Failed to delete: %s - %s\n", data->path, err->message);
+
+	g_clear_error(&err);
+
+	return result;
+}
+
+static void
+deleter_thread(GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable)
+{
+	_purple_logger_deleter_callback_data *callback_data =
+		g_simple_async_result_get_op_res_gpointer(simple);
+	PurpleLogCommonLoggerData *data = callback_data->log->logger_data;
+	GError *err = NULL;
+	GFile *file;
+
+	if (data == NULL || data->path == NULL) {
+		g_simple_async_result_set_error(simple, G_IO_ERROR, G_IO_ERROR_FAILED,
+			_("Unable to get log path"));
+
+		return;
 	}
+
+	file = g_file_new_for_path(data->path);
+
+	if (!g_file_delete(file, cancellable, &err))
+		g_simple_async_result_set_from_error(simple, err);
 	else
-	{
-		/* I'm not sure that g_unlink() will ever return
-		 * something other than 0 or -1. -- rlaager */
-		purple_debug_error("log", "Failed to delete: %s\n", data->path);
-	}
+		g_simple_async_result_set_op_res_gboolean(simple, TRUE);
 
-	return FALSE;
+	g_object_unref(file);
+	g_clear_error(&err);
 }
 
-gboolean purple_log_common_is_deletable(PurpleLog *log)
+void
+purple_log_common_deleter_async(PurpleLog *log, gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata)
 {
+	_purple_logger_deleter_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+
+	g_return_if_fail(log != NULL);
+
+	callback_data = g_new0(_purple_logger_deleter_callback_data, 1);
+	callback_data->log = log;
+
+	simple = g_simple_async_result_new(NULL, cb, userdata, purple_log_common_deleter_async);
+
+	g_simple_async_result_set_op_res_gpointer(simple, callback_data, g_free);
+	g_simple_async_result_run_in_thread(simple, deleter_thread, io_priority, cancellable);
+
+	g_object_unref(simple);
+}
+
+gboolean
+purple_log_common_deleter_finish(PurpleLog *log, GAsyncResult *res, GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = G_SIMPLE_ASYNC_RESULT(res);
+
+	if (g_simple_async_result_propagate_error(simple, error))
+		return FALSE;
+
+	g_return_val_if_fail(g_simple_async_result_get_source_tag(simple) == purple_log_common_deleter_async, FALSE);
+
+	return g_simple_async_result_get_op_res_gboolean(simple);
+}
+
+gboolean
+purple_log_common_is_deletable(PurpleLog *log)
+{
 	PurpleLogCommonLoggerData *data;
-#ifndef _WIN32
-	gchar *dirname;
-#endif
 
 	g_return_val_if_fail(log != NULL, FALSE);
 
 	data = log->logger_data;
-	if (data == NULL)
-		return FALSE;
 
-	if (data->path == NULL)
+	if (data == NULL || data->path == NULL)
 		return FALSE;
 
-#ifndef _WIN32
-	dirname = g_path_get_dirname(data->path);
-	if (g_access(dirname, W_OK) == 0)
+#ifndef G_OS_WIN32
 	{
+		gchar *dirname = g_path_get_dirname(data->path);
+
+		if (g_access(dirname, W_OK) == 0) {
+			g_free(dirname);
+			return TRUE;
+		}
+
+		purple_debug_info("log", "access(%s) failed: %s\n", dirname, g_strerror(errno));
 		g_free(dirname);
-		return TRUE;
+
+		return FALSE;
 	}
-	purple_debug_info("log", "access(%s) failed: %s\n", dirname, g_strerror(errno));
-	g_free(dirname);
 #else
 	/* Unless and until someone writes equivalent win32 code,
 	 * we'll assume the file is deletable. */
 	return TRUE;
 #endif
-
-	return FALSE;
 }
 
-static char *process_txt_log(char *txt, char *to_free)
+static gchar *
+process_txt_log(gchar *txt, gchar *to_free)
 {
-	char *tmp;
+	gchar *tmp;
 
 	/* The to_free argument allows us to save a
 	 * g_strdup() in some cases. */
@@ -1259,18 +2909,17 @@ static char *process_txt_log(char *txt, 
  ** XML LOGGER **
  ****************/
 
-static const char *str_from_msg_type (PurpleMessageFlags type)
+static const gchar *
+str_from_msg_type (PurpleMessageFlags type)
 {
-
-		return "";
-
+	return "";
 }
 
-static void xml_logger_write(PurpleLog *log,
-			     PurpleMessageFlags type,
-			     const char *from, time_t time, const char *message)
+static void
+xml_logger_write(PurpleLog *log, PurpleMessageFlags type, const gchar *from, time_t time,
+	const gchar *message)
 {
-	char *xhtml = NULL;
+	gchar *xhtml = NULL;
 
 	if (!log->logger_data) {
 		/* This log is new.  We could use the loggers 'new' function, but
@@ -1278,11 +2927,11 @@ static void xml_logger_write(PurpleLog *
 		 * that you open a convo with someone, but don't say anything.
 		 */
 		struct tm *tm;
-		const char *tz;
-		const char *date;
-		char *dir = purple_log_get_log_dir(log->type, log->name, log->account);
-		char *name;
-		char *filename;
+		const gchar *tz;
+		const gchar *date;
+		gchar *dir = purple_log_get_log_dir(log->type, log->name, log->account);
+		gchar *name;
+		gchar *filename;
 
 		if (dir == NULL)
 			return;
@@ -1338,7 +2987,8 @@ static void xml_logger_write(PurpleLog *
 	g_free(xhtml);
 }
 
- static void xml_logger_finalize(PurpleLog *log)
+ static void
+ xml_logger_finalize(PurpleLog *log)
 {
 	if (log->logger_data) {
 		fprintf(log->logger_data, "</conversation>\n");
@@ -1347,7 +2997,8 @@ static void xml_logger_write(PurpleLog *
 	}
 }
 
-static GList *xml_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
+static GList *
+xml_logger_list(PurpleLogType type, const gchar *sn, PurpleAccount *account)
 {
 	return purple_log_common_lister(type, sn, account, ".xml", &xml_logger);
 }
@@ -1368,22 +3019,22 @@ static PurpleLogLogger xml_logger =  {
  ** HTML LOGGER *************
  ****************************/
 
-static gsize html_logger_write(PurpleLog *log, PurpleMessageFlags type,
-							  const char *from, time_t time, const char *message)
+static gsize
+html_logger_write(PurpleLog *log, PurpleMessageFlags type, const gchar *from, time_t time,
+	const gchar *message)
 {
-	char *msg_fixed;
-	char *image_corrected_msg;
-	char *date;
-	char *header;
-	char *escaped_from;
-	PurplePlugin *plugin = purple_find_prpl(purple_account_get_protocol_id(log->account));
-	PurpleLogCommonLoggerData *data = log->logger_data;
-	gsize written = 0;
+	PurpleLogCommonLoggerData *data;
+	PurplePlugin *plugin;
+	const gchar *date_full, *prpl;
+	gchar *date, *escaped_from, *header, *image_corrected_msg, *msg_fixed;
+	gsize written;
 
+	plugin = purple_find_prpl(purple_account_get_protocol_id(log->account));
+	data = log->logger_data;
+	written = 0;
+
 	if(!data) {
-		const char *prpl =
-			PURPLE_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL);
-		const char *date;
+		prpl = PURPLE_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL);
 		purple_log_common_writer(log, ".html");
 
 		data = log->logger_data;
@@ -1392,17 +3043,18 @@ static gsize html_logger_write(PurpleLog
 		if(!data->file)
 			return 0;
 
-		date = purple_date_format_full(localtime(&log->time));
+		date_full = purple_date_format_full(localtime(&log->time));
 
 		written += fprintf(data->file, "<html><head>");
 		written += fprintf(data->file, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">");
 		written += fprintf(data->file, "<title>");
+
 		if (log->type == PURPLE_LOG_SYSTEM)
 			header = g_strdup_printf("System log for account %s (%s) connected at %s",
-					purple_account_get_username(log->account), prpl, date);
+				purple_account_get_username(log->account), prpl, date_full);
 		else
 			header = g_strdup_printf("Conversation with %s at %s on %s (%s)",
-					log->name, date, purple_account_get_username(log->account), prpl);
+				log->name, date_full, purple_account_get_username(log->account), prpl);
 
 		written += fprintf(data->file, "%s", header);
 		written += fprintf(data->file, "</title></head><body>");
@@ -1437,7 +3089,7 @@ static gsize html_logger_write(PurpleLog
 			written += fprintf(data->file, "<font color=\"#FF0000\"><font size=\"2\">(%s)</font><b> %s</b></font><br/>\n", date, msg_fixed);
 		else if (type & PURPLE_MESSAGE_WHISPER)
 			written += fprintf(data->file, "<font color=\"#6C2585\"><font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n",
-					date, escaped_from, msg_fixed);
+				date, escaped_from, msg_fixed);
 		else if (type & PURPLE_MESSAGE_AUTO_RESP) {
 			if (type & PURPLE_MESSAGE_SEND)
 				written += fprintf(data->file, _("<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s &lt;AUTO-REPLY&gt;:</b></font> %s<br/>\n"), date, escaped_from, msg_fixed);
@@ -1446,23 +3098,24 @@ static gsize html_logger_write(PurpleLog
 		} else if (type & PURPLE_MESSAGE_RECV) {
 			if(purple_message_meify(msg_fixed, -1))
 				written += fprintf(data->file, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n",
-						date, escaped_from, msg_fixed);
+					date, escaped_from, msg_fixed);
 			else
 				written += fprintf(data->file, "<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
-						date, escaped_from, msg_fixed);
+					date, escaped_from, msg_fixed);
 		} else if (type & PURPLE_MESSAGE_SEND) {
 			if(purple_message_meify(msg_fixed, -1))
 				written += fprintf(data->file, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n",
-						date, escaped_from, msg_fixed);
+					date, escaped_from, msg_fixed);
 			else
 				written += fprintf(data->file, "<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
-						date, escaped_from, msg_fixed);
+					date, escaped_from, msg_fixed);
 		} else {
 			purple_debug_error("log", "Unhandled message type.\n");
 			written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n",
-						date, escaped_from, msg_fixed);
+				date, escaped_from, msg_fixed);
 		}
 	}
+
 	g_free(date);
 	g_free(msg_fixed);
 	g_free(escaped_from);
@@ -1471,39 +3124,285 @@ static gsize html_logger_write(PurpleLog
 	return written;
 }
 
-static void html_logger_finalize(PurpleLog *log)
+static void
+html_logger_write_thread(GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable)
 {
+	_purple_log_logger_write_callback_data *callback_data =
+		g_simple_async_result_get_op_res_gpointer(simple);
+	PurpleLog *log = callback_data->log;
 	PurpleLogCommonLoggerData *data = log->logger_data;
-	if (data) {
-		if(data->file) {
+	PurpleLogType type = callback_data->type;
+	GError *err = NULL;
+	GFile *file;
+	GFileOutputStream *stream;
+	GOutputStream *out_stream;
+	gchar *date, *escaped_from, *message = callback_data->message, *from = callback_data->from;
+	gchar *image_corrected_msg, *msg_fixed, *line;
+	gssize written, size = 0;
+	gboolean write_header;
+
+	if (data == NULL) {
+		/* This log is new.  We could use the loggers 'new' function, but
+		 * creating a new file there would result in empty files in the case
+		 * that you open a convo with someone, but don't say anything.
+		 */
+		purple_log_common_writer(log, ".html");
+		data = log->logger_data;
+		write_header = TRUE;
+	} else
+		write_header = FALSE;
+
+	/* If we can't write to the file, give up before we hurt ourselves */
+	if (data == NULL || data->path == NULL) {
+		g_simple_async_result_set_error(simple,
+			G_IO_ERROR,
+			G_IO_ERROR_FAILED,
+			_("Unable to find log path"));
+
+		return;
+	}
+
+	file = g_file_new_for_path(data->path);
+	stream = g_file_append_to(file, G_FILE_CREATE_NONE, cancellable, &err);
+
+	if (stream == NULL) {
+		g_simple_async_result_set_from_error(simple, err);
+
+		g_object_unref(file);
+		g_clear_error(&err);
+
+		return;
+	}
+
+	g_clear_error(&err);
+	out_stream = G_OUTPUT_STREAM(stream);
+
+	if (write_header) {
+		PurplePlugin *plugin;
+		const gchar *prpl;
+		const gchar *date_full;
+		gchar *header;
+
+		plugin = purple_find_prpl(purple_account_get_protocol_id(log->account));
+		prpl = PURPLE_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL);
+		date_full = purple_date_format_full(localtime(&log->time));
+
+		if (log->type == PURPLE_LOG_SYSTEM)
+			header = g_strdup_printf("System log for account %s (%s) connected at %s",
+				purple_account_get_username(log->account), prpl, date_full);
+		else
+			header = g_strdup_printf("Conversation with %s at %s on %s (%s)",
+				log->name, date_full,
+				purple_account_get_username(log->account), prpl);
+
+		line = g_strdup_printf("<html><head>"
+			"<meta http-equiv=\"content-type\" "
+			"content=\"text/html; charset=UTF-8\">"
+			"<title>"
+			"%s"
+			"</title></head><body>"
+			"<h3>%s</h3>\n",
+			header, header);
+
+		g_free(header);
+
+		written = g_output_stream_write(out_stream, line,
+			strlen(line), cancellable, &err);
+		g_free(line);
+
+		if (written < 0) {
+			g_simple_async_result_set_from_error(simple, err);
+
+			g_object_unref(file);
+			g_object_unref(stream);
+			g_clear_error(&err);
+
+			return;
+		}
+
+		g_clear_error(&err);
+		size += written;
+	}
+
+	escaped_from = g_markup_escape_text(from, -1);
+	image_corrected_msg = convert_image_tags(log, message);
+	purple_markup_html_to_xhtml(image_corrected_msg, &msg_fixed, NULL);
+
+	/* Yes, this breaks encapsulation.  But it's a static function and
+	 * this saves a needless strdup(). */
+	if (image_corrected_msg != message)
+		g_free(image_corrected_msg);
+
+	date = log_get_timestamp(log, callback_data->time);
+
+	if(log->type == PURPLE_LOG_SYSTEM){
+		line = g_strdup_printf("---- %s @ %s ----<br/>\n", msg_fixed, date);
+	} else {
+		if (type & PURPLE_MESSAGE_SYSTEM)
+			line = g_strdup_printf("<font size=\"2\">(%s)</font><b> %s</b><br/>\n", date, msg_fixed);
+		else if (type & PURPLE_MESSAGE_RAW)
+			line = g_strdup_printf("<font size=\"2\">(%s)</font> %s<br/>\n",
+				date, msg_fixed);
+		else if (type & PURPLE_MESSAGE_ERROR)
+			line = g_strdup_printf("<font color=\"#FF0000\"><font "
+				"size=\"2\">(%s)</font><b> %s</b></font><br/>\n",
+				date, msg_fixed);
+		else if (type & PURPLE_MESSAGE_WHISPER)
+			line = g_strdup_printf("<font color=\"#6C2585\"><font "
+				"size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n",
+				date, escaped_from, msg_fixed);
+		else if (type & PURPLE_MESSAGE_AUTO_RESP) {
+			if (type & PURPLE_MESSAGE_SEND)
+				line = g_strdup_printf(_("<font color=\"#16569E\"><font "
+					"size=\"2\">(%s)</font> <b>%s &lt;AUTO-REPLY&gt;:</b>"
+					"</font> %s<br/>\n"), date, escaped_from, msg_fixed);
+			else
+				line = g_strdup_printf(_("<font color=\"#A82F2F\"><font "
+					"size=\"2\">(%s)</font> <b>%s &lt;AUTO-REPLY&gt;:</b>"
+					"</font> %s<br/>\n"), date, escaped_from, msg_fixed);
+		} else if (type & PURPLE_MESSAGE_RECV) {
+			if (purple_message_meify(msg_fixed, -1))
+				line = g_strdup_printf("<font color=\"#062585\"><font "
+					"size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n",
+					date, escaped_from, msg_fixed);
+			else
+				line = g_strdup_printf("<font color=\"#A82F2F\"><font "
+					"size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
+					date, escaped_from, msg_fixed);
+		} else if (type & PURPLE_MESSAGE_SEND) {
+			if (purple_message_meify(msg_fixed, -1))
+				line = g_strdup_printf("<font color=\"#062585\"><font "
+					"size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n",
+					date, escaped_from, msg_fixed);
+			else
+				line = g_strdup_printf("<font color=\"#16569E\"><font "
+					"size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
+					date, escaped_from, msg_fixed);
+		} else {
+			line = g_strdup_printf("<font size=\"2\">(%s)</font>"
+				"<b> %s:</b></font> %s<br/>\n",
+				date, escaped_from, msg_fixed);
+		}
+	}
+
+	written = g_output_stream_write(out_stream, line, strlen(line),
+		cancellable, &err);
+
+	if (written < 0)
+		g_simple_async_result_set_from_error(simple, err);
+	else
+		g_simple_async_result_set_op_res_gssize(simple, size + written);
+
+	g_clear_error(&err);
+	g_free(date);
+	g_free(msg_fixed);
+	g_free(escaped_from);
+	g_object_unref(file);
+	g_object_unref(stream);
+}
+
+static void
+html_logger_write_async(PurpleLog *log, PurpleMessageFlags type, const gchar *from, time_t time,
+	const gchar *message, gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb,
+	gpointer userdata)
+{
+	_purple_log_logger_write_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+
+	callback_data = g_new0(_purple_log_logger_write_callback_data, 1);
+	callback_data->log = log;
+	callback_data->type = type;
+	callback_data->from = g_strdup(from);
+	callback_data->time = time;
+	callback_data->message = g_strdup(message);
+
+	simple = g_simple_async_result_new(NULL, cb, userdata, purple_log_write_async);
+
+	g_simple_async_result_set_op_res_gpointer(simple, callback_data,
+		purple_log_logger_write_callback_data_free);
+	g_simple_async_result_run_in_thread(simple, html_logger_write_thread,
+		io_priority, cancellable);
+
+	g_object_unref(simple);
+}
+
+static void
+html_logger_finalize(PurpleLog *log)
+{
+	PurpleLogCommonLoggerData *data = log->logger_data;
+
+	if (data != NULL) {
+		if (data->file != NULL) {
 			fprintf(data->file, "</body></html>\n");
 			fclose(data->file);
 		}
-		g_free(data->path);
 
+		g_free(data->path);
 		g_slice_free(PurpleLogCommonLoggerData, data);
 	}
 }
 
-static GList *html_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
+static GList *
+html_logger_list(PurpleLogType type, const gchar *sn, PurpleAccount *account)
 {
-	return purple_log_common_lister(type, sn, account, ".html", html_logger);
+	GList *list;
+
+	G_LOCK(html_logger);
+	list = purple_log_common_lister(type, sn, account, ".html", html_logger);
+	G_UNLOCK(html_logger);
+
+	return list;
 }
 
-static GList *html_logger_list_syslog(PurpleAccount *account)
+static void
+html_logger_list_async(PurpleLogType type, const gchar *sn, PurpleAccount *account,
+	gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata)
 {
-	return purple_log_common_lister(PURPLE_LOG_SYSTEM, ".system", account, ".html", html_logger);
+	G_LOCK(html_logger);
+	purple_log_common_lister_async(type, sn, account, ".html", html_logger,
+		io_priority, cancellable, cb, userdata);
+	G_UNLOCK(html_logger);
 }
 
-static char *html_logger_read(PurpleLog *log, PurpleLogReadFlags *flags)
+static GList *
+html_logger_list_syslog(PurpleAccount *account)
 {
-	char *read;
-	PurpleLogCommonLoggerData *data = log->logger_data;
-	*flags = PURPLE_LOG_READ_NO_NEWLINE;
+	GList *list;
+
+	G_LOCK(html_logger);
+	list = purple_log_common_lister(PURPLE_LOG_SYSTEM, ".system", account, ".html", html_logger);
+	G_UNLOCK(html_logger);
+
+	return list;
+}
+
+static void
+html_logger_list_syslog_async(PurpleAccount *account, gint io_priority,
+	GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata)
+{
+	G_LOCK(html_logger);
+	purple_log_common_lister_async(PURPLE_LOG_SYSTEM, ".system", account, ".html", html_logger,
+		io_priority, cancellable, cb, userdata);
+	G_UNLOCK(html_logger);
+}
+
+static gchar *
+html_logger_read(PurpleLog *log, PurpleLogReadFlags *flags)
+{
+	PurpleLogCommonLoggerData *data;
+	gchar *read, *minus_header;
+
+	data = log->logger_data;
+
+	if (flags != NULL)
+		*flags = PURPLE_LOG_READ_NO_NEWLINE;
+
 	if (!data || !data->path)
-		return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>"));
+		return g_strdup_printf("<font color=\"red\"><b>%s</b></font>",
+			_("Unable to find log path"));
+
 	if (g_file_get_contents(data->path, &read, NULL, NULL)) {
-		char *minus_header = strchr(read, '\n');
+		minus_header = strchr(read, '\n');
 
 		if (!minus_header)
 			return read;
@@ -1513,37 +3412,126 @@ static char *html_logger_read(PurpleLog 
 
 		return minus_header;
 	}
-	return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data->path);
+
+	return g_strdup_printf("<font color=\"red\"><b>%s: %s</b></font>",
+		_("Could not read file"), data->path);
 }
 
-static int html_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account)
+static void
+html_logger_read_thread(GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable)
 {
+	_purple_log_logger_read_callback_data *callback_data =
+		g_simple_async_result_get_op_res_gpointer(simple);
+	PurpleLog *log = callback_data->log;
+	PurpleLogCommonLoggerData *data = log->logger_data;
+	PurpleLogReadFlags *flags = callback_data->flags;
+	GError *err = NULL;
+	GFile *file;
+	gchar *read, *minus_header;
+
+	if (flags != NULL)
+		*flags = PURPLE_LOG_READ_NO_NEWLINE;
+
+	if (data == NULL || data->path == NULL) {
+		g_simple_async_result_set_error(simple,
+			G_FILE_ERROR,
+			G_IO_ERROR_NOT_FOUND,
+			"<font color=\"red\"><b>%s</b></font>",
+			_("Unable to find log path"));
+
+		return;
+	}
+
+	file = g_file_new_for_path(data->path);
+
+	if (!g_file_load_contents(file, cancellable, &read, NULL, NULL, &err)) {
+		g_simple_async_result_set_error(simple,
+			err->domain,
+			err->code,
+			"<font color=\"red\"><b>%s</b></font>",
+			err->message);
+
+		g_clear_error(&err);
+
+		return;
+	}
+
+	g_clear_error(&err);
+
+	minus_header = strchr(read, '\n');
+
+	if (minus_header == NULL)
+		minus_header = read;
+	else {
+		minus_header = g_strdup(minus_header + 1);
+		g_free(read);
+	}
+
+	purple_str_strip_char(minus_header, '\r');
+
+	g_simple_async_result_set_op_res_gpointer(simple, minus_header, g_free);
+}
+
+static void
+html_logger_read_async(PurpleLog *log, PurpleLogReadFlags *flags, gint io_priority,
+	GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata)
+{
+	_purple_log_logger_read_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+
+	callback_data = g_new0(_purple_log_logger_read_callback_data, 1);
+	callback_data->flags = flags;
+	callback_data->log = log;
+
+	simple = g_simple_async_result_new(NULL, cb, userdata, purple_log_read_async);
+
+	g_simple_async_result_set_op_res_gpointer(simple, callback_data, g_free);
+	g_simple_async_result_run_in_thread(simple, html_logger_read_thread,
+		io_priority, cancellable);
+
+	g_object_unref(simple);
+}
+
+static gint
+html_logger_total_size(PurpleLogType type, const gchar *name, PurpleAccount *account)
+{
 	return purple_log_common_total_sizer(type, name, account, ".html");
 }
 
+static void
+html_logger_total_size_async(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata)
+{
+	purple_log_common_total_sizer_async(type, name, account, ".html",
+		io_priority, cancellable, cb, userdata);
+}
 
+
 /****************************
  ** PLAIN TEXT LOGGER *******
  ****************************/
 
-static gsize txt_logger_write(PurpleLog *log,
-							 PurpleMessageFlags type,
-							 const char *from, time_t time, const char *message)
+static gsize
+txt_logger_write(PurpleLog *log, PurpleMessageFlags type, const gchar *from, time_t time,
+	const gchar *message)
 {
-	char *date;
-	PurplePlugin *plugin = purple_find_prpl(purple_account_get_protocol_id(log->account));
-	PurpleLogCommonLoggerData *data = log->logger_data;
-	char *stripped = NULL;
+	PurplePlugin *plugin;
+	PurpleLogCommonLoggerData *data;
+	const gchar *prpl;
+	gchar *stripped, *date;
+	gsize written;
 
-	gsize written = 0;
+	plugin = purple_find_prpl(purple_account_get_protocol_id(log->account));
+	data = log->logger_data;
+	stripped = NULL;
+	written = 0;
 
 	if (data == NULL) {
 		/* This log is new.  We could use the loggers 'new' function, but
 		 * creating a new file there would result in empty files in the case
 		 * that you open a convo with someone, but don't say anything.
 		 */
-		const char *prpl =
-			PURPLE_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL);
+		prpl = PURPLE_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL);
 		purple_log_common_writer(log, ".txt");
 
 		data = log->logger_data;
@@ -1576,14 +3564,12 @@ static gsize txt_logger_write(PurpleLog 
 			type & PURPLE_MESSAGE_RECV) {
 			if (type & PURPLE_MESSAGE_AUTO_RESP) {
 				written += fprintf(data->file, _("(%s) %s <AUTO-REPLY>: %s\n"), date,
-						from, stripped);
+					from, stripped);
 			} else {
-				if(purple_message_meify(stripped, -1))
-					written += fprintf(data->file, "(%s) ***%s %s\n", date, from,
-							stripped);
+				if (purple_message_meify(stripped, -1))
+					written += fprintf(data->file, "(%s) ***%s %s\n", date, from, stripped);
 				else
-					written += fprintf(data->file, "(%s) %s: %s\n", date, from,
-							stripped);
+					written += fprintf(data->file, "(%s) %s: %s\n", date, from, stripped);
 			}
 		} else if (type & PURPLE_MESSAGE_SYSTEM ||
 			type & PURPLE_MESSAGE_ERROR ||
@@ -1592,13 +3578,15 @@ static gsize txt_logger_write(PurpleLog 
 		else if (type & PURPLE_MESSAGE_NO_LOG) {
 			/* This shouldn't happen */
 			g_free(stripped);
+
 			return written;
 		} else if (type & PURPLE_MESSAGE_WHISPER)
 			written += fprintf(data->file, "(%s) *%s* %s", date, from, stripped);
 		else
 			written += fprintf(data->file, "(%s) %s%s %s\n", date, from ? from : "",
-					from ? ":" : "", stripped);
+				from ? ":" : "", stripped);
 	}
+
 	g_free(date);
 	g_free(stripped);
 	fflush(data->file);
@@ -1606,35 +3594,244 @@ static gsize txt_logger_write(PurpleLog 
 	return written;
 }
 
-static void txt_logger_finalize(PurpleLog *log)
+static void
+txt_logger_write_thread(GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable)
 {
+	_purple_log_logger_write_callback_data *callback_data =
+		g_simple_async_result_get_op_res_gpointer(simple);
+	PurpleLog *log = callback_data->log;
 	PurpleLogCommonLoggerData *data = log->logger_data;
-	if (data) {
-		if(data->file)
+	PurpleLogType type = callback_data->type;
+	GError *err = NULL;
+	GFile *file;
+	GFileOutputStream *stream;
+	GOutputStream *out_stream;
+	const gchar *from = callback_data->from, *message = callback_data->message;
+	gchar *stripped = NULL, *date, *line;
+	gssize written, size = 0;
+	gboolean write_header;
+
+	if (type & PURPLE_MESSAGE_NO_LOG) {
+		g_simple_async_result_set_error(simple,
+			G_IO_ERROR,
+			G_IO_ERROR_FAILED,
+			_("Trying to log when flagged not to"));
+
+		return;
+	}
+
+	if (data == NULL) {
+		/* This log is new.  We could use the loggers 'new' function, but
+		 * creating a new file there would result in empty files in the case
+		 * that you open a convo with someone, but don't say anything.
+		 */
+		purple_log_common_writer(log, ".txt");
+		data = log->logger_data;
+		write_header = TRUE;
+	} else
+		write_header = FALSE;
+
+	/* If we can't write to the file, give up before we hurt ourselves */
+	if (data == NULL || data->path == NULL) {
+		g_simple_async_result_set_error(simple,
+			G_IO_ERROR,
+			G_IO_ERROR_FAILED,
+			_("Unable to find log path"));
+
+		return;
+	}
+
+	file = g_file_new_for_path(data->path);
+	stream = g_file_append_to(file, G_FILE_CREATE_NONE, cancellable, &err);
+
+	if (stream == NULL) {
+		g_simple_async_result_set_from_error(simple, err);
+
+		g_object_unref(file);
+		g_clear_error(&err);
+
+		return;
+	}
+
+	g_clear_error(&err);
+	out_stream = G_OUTPUT_STREAM(stream);
+
+	if (write_header) {
+		PurplePlugin *plugin;
+		const gchar *prpl;
+
+		plugin = purple_find_prpl(purple_account_get_protocol_id(log->account));
+		prpl = PURPLE_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL);
+
+		if (log->type == PURPLE_LOG_SYSTEM)
+			line = g_strdup_printf("System log for account %s (%s) connected at %s\n",
+				purple_account_get_username(log->account), prpl,
+				purple_date_format_full(localtime(&log->time)));
+		else
+			line = g_strdup_printf("Conversation with %s at %s on %s (%s)\n",
+				log->name, purple_date_format_full(localtime(&log->time)),
+				purple_account_get_username(log->account), prpl);
+
+		written = g_output_stream_write(out_stream, line,
+			strlen(line), cancellable, &err);
+		g_free(line);
+
+		if (written < 0) {
+			g_simple_async_result_set_from_error(simple, err);
+
+			g_object_unref(file);
+			g_object_unref(stream);
+			g_clear_error(&err);
+
+			return;
+		}
+
+		g_clear_error(&err);
+		size += written;
+	}
+
+	stripped = purple_markup_strip_html(message);
+	date = log_get_timestamp(log, callback_data->time);
+
+	if (log->type == PURPLE_LOG_SYSTEM)
+		line = g_strdup_printf("---- %s @ %s ----\n", stripped, date);
+	else {
+		if (type & PURPLE_MESSAGE_SEND ||
+			type & PURPLE_MESSAGE_RECV)
+		{
+			if (type & PURPLE_MESSAGE_AUTO_RESP)
+				line = g_strdup_printf(_("(%s) %s <AUTO-REPLY>: %s\n"), date, from, stripped);
+			else {
+				if (purple_message_meify(stripped, -1))
+					line = g_strdup_printf("(%s) ***%s %s\n", date, from, stripped);
+				else
+					line = g_strdup_printf("(%s) %s: %s\n", date, from, stripped);
+			}
+		} else if (type & PURPLE_MESSAGE_SYSTEM ||
+			type & PURPLE_MESSAGE_ERROR ||
+			type & PURPLE_MESSAGE_RAW)
+			line = g_strdup_printf("(%s) %s\n", date, stripped);
+		else if (type & PURPLE_MESSAGE_WHISPER)
+			line = g_strdup_printf("(%s) *%s* %s", date, from, stripped);
+		else
+			line = g_strdup_printf("(%s) %s%s %s\n", date, from ? from : "",
+				from ? ":" : "", stripped);
+	}
+
+	written = g_output_stream_write(out_stream, line, strlen(line),
+		cancellable, &err);
+
+	if (written < 0)
+		g_simple_async_result_set_from_error(simple, err);
+	else
+		g_simple_async_result_set_op_res_gssize(simple, size + written);
+
+	g_clear_error(&err);
+	g_free(line);
+	g_free(date);
+	g_free(stripped);
+	g_object_unref(file);
+	g_object_unref(stream);
+}
+
+static void
+txt_logger_write_async(PurpleLog *log, PurpleMessageFlags type, const gchar *from, time_t time,
+	const gchar *message, gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb,
+	gpointer userdata)
+{
+	_purple_log_logger_write_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+
+	callback_data = g_new0(_purple_log_logger_write_callback_data, 1);
+	callback_data->log = log;
+	callback_data->type = type;
+	callback_data->from = g_strdup(from);
+	callback_data->time = time;
+	callback_data->message = g_strdup(message);
+
+	simple = g_simple_async_result_new(NULL, cb, userdata, purple_log_write_async);
+
+	g_simple_async_result_set_op_res_gpointer(simple, callback_data,
+		purple_log_logger_write_callback_data_free);
+	g_simple_async_result_run_in_thread(simple, txt_logger_write_thread,
+		io_priority, cancellable);
+
+	g_object_unref(simple);
+}
+
+static void
+txt_logger_finalize(PurpleLog *log)
+{
+	PurpleLogCommonLoggerData *data = log->logger_data;
+
+	if (data != NULL) {
+		if (data->file != NULL)
 			fclose(data->file);
+
 		g_free(data->path);
-
 		g_slice_free(PurpleLogCommonLoggerData, data);
 	}
 }
 
-static GList *txt_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
+static GList *
+txt_logger_list(PurpleLogType type, const gchar *sn, PurpleAccount *account)
 {
-	return purple_log_common_lister(type, sn, account, ".txt", txt_logger);
+	GList *list;
+
+	G_LOCK(txt_logger);
+	list = purple_log_common_lister(type, sn, account, ".txt", txt_logger);
+	G_UNLOCK(txt_logger);
+
+	return list;
 }
 
-static GList *txt_logger_list_syslog(PurpleAccount *account)
+static void
+txt_logger_list_async(PurpleLogType type, const gchar *sn, PurpleAccount *account,
+	gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata)
 {
-	return purple_log_common_lister(PURPLE_LOG_SYSTEM, ".system", account, ".txt", txt_logger);
+	G_LOCK(txt_logger);
+	purple_log_common_lister_async(type, sn, account, ".txt", txt_logger,
+		io_priority, cancellable, cb, userdata);
+	G_UNLOCK(txt_logger);
 }
 
-static char *txt_logger_read(PurpleLog *log, PurpleLogReadFlags *flags)
+static GList *
+txt_logger_list_syslog(PurpleAccount *account)
 {
-	char *read, *minus_header;
-	PurpleLogCommonLoggerData *data = log->logger_data;
-	*flags = 0;
+	GList *list;
+
+	G_LOCK(txt_logger);
+	list = purple_log_common_lister(PURPLE_LOG_SYSTEM, ".system", account, ".txt", txt_logger);
+	G_UNLOCK(txt_logger);
+
+	return list;
+}
+
+static void
+txt_logger_list_syslog_async(PurpleAccount *account, gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata)
+{
+	G_LOCK(txt_logger);
+	purple_log_common_lister_async(PURPLE_LOG_SYSTEM, ".system", account, ".txt", txt_logger,
+		io_priority, cancellable, cb, userdata);
+	G_UNLOCK(txt_logger);
+}
+
+static gchar *
+txt_logger_read(PurpleLog *log, PurpleLogReadFlags *flags)
+{
+	PurpleLogCommonLoggerData *data;
+	gchar *read, *minus_header;
+
+	data = log->logger_data;
+
+	if (flags != NULL)
+		*flags = 0;
+
 	if (!data || !data->path)
-		return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>"));
+		return g_strdup_printf("<font color=\"red\"><b>%s</b></font>",
+			_("Unable to find log path"));
+
 	if (g_file_get_contents(data->path, &read, NULL, NULL)) {
 		minus_header = strchr(read, '\n');
 
@@ -1643,15 +3840,99 @@ static char *txt_logger_read(PurpleLog *
 		else
 			return process_txt_log(read, NULL);
 	}
-	return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data->path);
+
+	return g_strdup_printf("<font color=\"red\"><b>%s: %s</b></font>",
+		_("Could not read file"), data->path);
 }
 
-static int txt_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account)
+static void
+txt_logger_read_thread(GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable)
 {
+	_purple_log_logger_read_callback_data *callback_data =
+		g_simple_async_result_get_op_res_gpointer(simple);
+	PurpleLog *log = callback_data->log;
+	PurpleLogCommonLoggerData *data = log->logger_data;
+	PurpleLogReadFlags *flags = callback_data->flags;
+	GError *err = NULL;
+	GFile *file;
+	gchar *read, *minus_header;
+
+	if (flags != NULL)
+		*flags = 0;
+
+	if (data == NULL || data->path == NULL) {
+		g_simple_async_result_set_error(simple,
+			G_FILE_ERROR,
+			G_IO_ERROR_NOT_FOUND,
+			"<font color=\"red\"><b>%s</b></font>",
+			_("Unable to find log path"));
+
+		return;
+	}
+
+	file = g_file_new_for_path(data->path);
+
+	if (!g_file_load_contents(file, cancellable, &read, NULL, NULL, &err)) {
+		g_simple_async_result_set_error(simple,
+			err->domain,
+			err->code,
+			"<font color=\"red\"><b>%s</b></font>",
+			err->message);
+
+		g_clear_error(&err);
+
+		return;
+	}
+
+	g_clear_error(&err);
+
+	minus_header = strchr(read, '\n');
+
+	if (minus_header != NULL)
+		read = process_txt_log(minus_header + 1, read);
+	else
+		read = process_txt_log(read, NULL);
+
+	purple_str_strip_char(read, '\r');
+
+	g_simple_async_result_set_op_res_gpointer(simple, read, g_free);
+}
+
+static void
+txt_logger_read_async(PurpleLog *log, PurpleLogReadFlags *flags, gint io_priority,
+	GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata)
+{
+	_purple_log_logger_read_callback_data *callback_data;
+	GSimpleAsyncResult *simple;
+
+	callback_data = g_new0(_purple_log_logger_read_callback_data, 1);
+	callback_data->flags = flags;
+	callback_data->log = log;
+
+	simple = g_simple_async_result_new(NULL, cb, userdata, purple_log_read_async);
+
+	g_simple_async_result_set_op_res_gpointer(simple, callback_data, g_free);
+	g_simple_async_result_run_in_thread(simple, txt_logger_read_thread,
+		io_priority, cancellable);
+
+	g_object_unref(simple);
+}
+
+static gint
+txt_logger_total_size(PurpleLogType type, const gchar *name, PurpleAccount *account)
+{
 	return purple_log_common_total_sizer(type, name, account, ".txt");
 }
 
+static void
+txt_logger_total_size_async(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata)
+{
+	purple_log_common_total_sizer_async(type, name, account, ".txt",
+		io_priority, cancellable, cb, userdata);
+}
 
+
 /****************
  * OLD LOGGER ***
  ****************/
@@ -1660,80 +3941,77 @@ static int txt_logger_total_size(PurpleL
  * old logs in the log viewer transparently.
  */
 
-struct old_logger_data {
+typedef struct {
 	PurpleStringref *pathref;
-	int offset;
-	int length;
-};
+	gint offset;
+	gint length;
+} old_logger_data;
 
-static GList *old_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
+static GList *
+old_logger_list(PurpleLogType type, const gchar *sn, PurpleAccount *account)
 {
-	char *logfile = g_strdup_printf("%s.log", purple_normalize(account, sn));
-	char *pathstr = g_build_filename(purple_user_dir(), "logs", logfile, NULL);
-	PurpleStringref *pathref = purple_stringref_new(pathstr);
+	old_logger_data *data;
+	PurpleStringref *pathref;
+	PurpleLog *log;
+	GList *list;
+	FILE *index, *file;
+	time_t log_last_modified, lasttime;
 	struct stat st;
-	time_t log_last_modified;
-	FILE *index;
-	FILE *file;
-	int index_fd;
-	char *index_tmp;
-	char buf[BUF_LONG];
 	struct tm tm;
-	char month[4];
-	struct old_logger_data *data = NULL;
-	int logfound = 0;
-	int lastoff = 0;
-	int newlen;
-	time_t lasttime = 0;
+	gchar *logfile, *pathstr, *index_tmp, buf[BUF_LONG], month[4], convostart[32], *temp;
+	gint index_fd, logfound, lastoff, newlen, length, offset;
+	gulong idx_time;
 
-	PurpleLog *log = NULL;
-	GList *list = NULL;
+	logfile = g_strdup_printf("%s.log", purple_normalize(account, sn));
+	pathstr = g_build_filename(purple_user_dir(), "logs", logfile, NULL);
+	pathref = purple_stringref_new(pathstr);
+	g_free(logfile);
 
-	g_free(logfile);
+	log = NULL;
+	list = NULL;
+	data = NULL;
 
-	if (g_stat(purple_stringref_value(pathref), &st))
-	{
+	logfound = 0;
+	lastoff = 0;
+	lasttime = 0;
+
+	if (g_stat(purple_stringref_value(pathref), &st)) {
 		purple_stringref_unref(pathref);
 		g_free(pathstr);
+
 		return NULL;
-	}
-	else
+	} else
 		log_last_modified = st.st_mtime;
 
 	/* Change the .log extension to .idx */
 	strcpy(pathstr + strlen(pathstr) - 3, "idx");
 
-	if (g_stat(pathstr, &st) == 0)
-	{
-		if (st.st_mtime < log_last_modified)
-		{
+	if (g_stat(pathstr, &st) == 0) {
+		if (st.st_mtime < log_last_modified) {
 			purple_debug_warning("log", "Index \"%s\" exists, but is older than the log.\n", pathstr);
-		}
-		else
-		{
+		} else {
 			/* The index file exists and is at least as new as the log, so open it. */
 			if (!(index = g_fopen(pathstr, "rb")))
 			{
 				purple_debug_error("log", "Failed to open index file \"%s\" for reading: %s\n",
-				                 pathstr, g_strerror(errno));
+					pathstr, g_strerror(errno));
 
 				/* Fall through so that we'll parse the log file. */
-			}
-			else
-			{
+			} else {
 				purple_debug_info("log", "Using index: %s\n", pathstr);
 				g_free(pathstr);
-				while (fgets(buf, BUF_LONG, index))
-				{
-					unsigned long idx_time;
-					if (sscanf(buf, "%d\t%d\t%lu", &lastoff, &newlen, &idx_time) == 3)
-					{
+
+				while (fgets(buf, BUF_LONG, index)) {
+					if (sscanf(buf, "%d\t%d\t%lu", &lastoff, &newlen, &idx_time) == 3) {
 						log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, -1, NULL);
+						log->time = (time_t) idx_time;
+
+						G_LOCK(old_logger);
 						log->logger = old_logger;
-						log->time = (time_t)idx_time;
+						G_UNLOCK(old_logger);
 
-						/* IMPORTANT: Always set all members of struct old_logger_data */
-						data = g_slice_new(struct old_logger_data);
+						/* IMPORTANT: Always set all members of old_logger_data */
+						data = g_slice_new(old_logger_data);
 
 						data->pathref = purple_stringref_ref(pathref);
 						data->offset = lastoff;
@@ -1743,6 +4021,7 @@ static GList *old_logger_list(PurpleLogT
 						list = g_list_prepend(list, log);
 					}
 				}
+
 				fclose(index);
 				purple_stringref_unref(pathref);
 
@@ -1753,40 +4032,42 @@ static GList *old_logger_list(PurpleLogT
 
 	if (!(file = g_fopen(purple_stringref_value(pathref), "rb"))) {
 		purple_debug_error("log", "Failed to open log file \"%s\" for reading: %s\n",
-		                   purple_stringref_value(pathref), g_strerror(errno));
+			purple_stringref_value(pathref), g_strerror(errno));
 		purple_stringref_unref(pathref);
 		g_free(pathstr);
+
 		return NULL;
 	}
 
 	index_tmp = g_strdup_printf("%s.XXXXXX", pathstr);
+
 	if ((index_fd = g_mkstemp(index_tmp)) == -1) {
 		purple_debug_error("log", "Failed to open index temp file: %s\n",
-		                 g_strerror(errno));
+			g_strerror(errno));
+
 		g_free(pathstr);
 		g_free(index_tmp);
 		index = NULL;
 	} else {
-		if ((index = fdopen(index_fd, "wb")) == NULL)
-		{
+		index = fdopen(index_fd, "wb");
+
+		if (index == NULL) {
 			purple_debug_error("log", "Failed to fdopen() index temp file: %s\n",
-			                 g_strerror(errno));
+				g_strerror(errno));
 			close(index_fd);
-			if (index_tmp != NULL)
-			{
+
+			if (index_tmp != NULL) {
 				g_unlink(index_tmp);
 				g_free(index_tmp);
 			}
+
 			g_free(pathstr);
 		}
 	}
 
 	while (fgets(buf, BUF_LONG, file)) {
 		if (strstr(buf, "---- New C") != NULL) {
-			int length;
-			int offset;
-			char convostart[32];
-			char *temp = strchr(buf, '@');
+			temp = strchr(buf, '@');
 
 			if (temp == NULL || strlen(temp) < 2)
 				continue;
@@ -1804,8 +4085,7 @@ static GList *old_logger_list(PurpleLogT
 						sizeof("<HR><BR><H3 Align=Center> ---- New Conversation @ ") +
 						sizeof("----</H3><BR>") - 2;
 				} else {
-					newlen -=
-						sizeof("---- New Conversation @ ") + sizeof("----") - 2;
+					newlen -= sizeof("---- New Conversation @ ") + sizeof("----") - 2;
 				}
 
 				if(strchr(buf, '\r'))
@@ -1813,11 +4093,14 @@ static GList *old_logger_list(PurpleLogT
 
 				if (newlen != 0) {
 					log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, -1, NULL);
+					log->time = lasttime;
+
+					G_LOCK(old_logger);
 					log->logger = old_logger;
-					log->time = lasttime;
+					G_UNLOCK(old_logger);
 
-					/* IMPORTANT: Always set all members of struct old_logger_data */
-					data = g_slice_new(struct old_logger_data);
+					/* IMPORTANT: Always set all members of old_logger_data */
+					data = g_slice_new(old_logger_data);
 
 					data->pathref = purple_stringref_ref(pathref);
 					data->offset = lastoff;
@@ -1828,7 +4111,8 @@ static GList *old_logger_list(PurpleLogT
 
 					/* XXX: There is apparently Is there a proper way to print a time_t? */
 					if (index != NULL)
-						fprintf(index, "%d\t%d\t%lu\n", data->offset, data->length, (unsigned long)log->time);
+						fprintf(index, "%d\t%d\t%lu\n", data->offset, data->length,
+							(unsigned long)log->time);
 				}
 			}
 
@@ -1837,8 +4121,9 @@ static GList *old_logger_list(PurpleLogT
 
 			g_snprintf(convostart, length, "%s", temp);
 			memset(&tm, 0, sizeof(tm));
-			sscanf(convostart, "%*s %s %d %d:%d:%d %d",
-			       month, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &tm.tm_year);
+			sscanf(convostart, "%*s %s %d %d:%d:%d %d", month, &tm.tm_mday,
+				&tm.tm_hour, &tm.tm_min, &tm.tm_sec, &tm.tm_year);
+
 			/* Ugly hack, in case current locale is not English */
 			if (purple_strequal(month, "Jan")) {
 				tm.tm_mon= 0;
@@ -1873,11 +4158,14 @@ static GList *old_logger_list(PurpleLogT
 	if (logfound) {
 		if ((newlen = ftell(file) - lastoff) != 0) {
 			log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, -1, NULL);
+			log->time = lasttime;
+
+			G_LOCK(old_logger);
 			log->logger = old_logger;
-			log->time = lasttime;
+			G_UNLOCK(old_logger);
 
-			/* IMPORTANT: Always set all members of struct old_logger_data */
-			data = g_slice_new(struct old_logger_data);
+			/* IMPORTANT: Always set all members of old_logger_data */
+			data = g_slice_new(old_logger_data);
 
 			data->pathref = purple_stringref_ref(pathref);
 			data->offset = lastoff;
@@ -1888,44 +4176,45 @@ static GList *old_logger_list(PurpleLogT
 
 			/* XXX: Is there a proper way to print a time_t? */
 			if (index != NULL)
-				fprintf(index, "%d\t%d\t%d\n", data->offset, data->length, (int)log->time);
+				fprintf(index, "%d\t%d\t%d\n", data->offset, data->length, (gint) log->time);
 		}
 	}
 
 	purple_stringref_unref(pathref);
 	fclose(file);
-	if (index != NULL)
-	{
+
+	if (index != NULL) {
 		fclose(index);
 
-		if (index_tmp == NULL)
-		{
+		if (index_tmp == NULL) {
 			g_free(pathstr);
 			g_return_val_if_reached(list);
 		}
 
-		if (g_rename(index_tmp, pathstr))
-		{
+		if (g_rename(index_tmp, pathstr)) {
 			purple_debug_warning("log", "Failed to rename index temp file \"%s\" to \"%s\": %s\n",
-			                   index_tmp, pathstr, g_strerror(errno));
+				index_tmp, pathstr, g_strerror(errno));
 			g_unlink(index_tmp);
-		}
-		else
+		} else
 			purple_debug_info("log", "Built index: %s\n", pathstr);
 
 		g_free(index_tmp);
 		g_free(pathstr);
 	}
+
 	return list;
 }
 
-static int old_logger_total_size(PurpleLogType type, const char *name, PurpleAccount *account)
+static gint
+old_logger_total_size(PurpleLogType type, const gchar *name, PurpleAccount *account)
 {
-	char *logfile = g_strdup_printf("%s.log", purple_normalize(account, name));
-	char *pathstr = g_build_filename(purple_user_dir(), "logs", logfile, NULL);
-	int size;
+	gchar *logfile, *pathstr;
+	gint size;
 	struct stat st;
 
+	logfile = g_strdup_printf("%s.log", purple_normalize(account, name));
+	pathstr = g_build_filename(purple_user_dir(), "logs", logfile, NULL);
+
 	if (g_stat(pathstr, &st))
 		size = 0;
 	else
@@ -1937,52 +4226,73 @@ static int old_logger_total_size(PurpleL
 	return size;
 }
 
-static char * old_logger_read (PurpleLog *log, PurpleLogReadFlags *flags)
+static gchar *
+old_logger_read(PurpleLog *log, PurpleLogReadFlags *flags)
 {
 	size_t result;
-	struct old_logger_data *data = log->logger_data;
-	const char *path = purple_stringref_value(data->pathref);
-	FILE *file = g_fopen(path, "rb");
-	char *read = g_malloc(data->length + 1);
+	old_logger_data *data;
+	const gchar *path;
+	FILE *file;
+	gchar *read;
+
+	data = log->logger_data;
+	path = purple_stringref_value(data->pathref);
+	file = g_fopen(path, "rb");
+	read = g_malloc(data->length + 1);
+
 	fseek(file, data->offset, SEEK_SET);
 	result = fread(read, data->length, 1, file);
+
 	if (result != 1)
 		purple_debug_error("log", "Unable to read from log file: %s\n", path);
+
 	fclose(file);
 	read[data->length] = '\0';
-	*flags = 0;
-	if (strstr(read, "<BR>"))
-	{
-		*flags |= PURPLE_LOG_READ_NO_NEWLINE;
+
+	if (flags != NULL)
+		*flags = 0;
+
+	if (strstr(read, "<BR>")) {
+		if (flags != NULL)
+			*flags |= PURPLE_LOG_READ_NO_NEWLINE;
+
 		return read;
 	}
 
 	return process_txt_log(read, NULL);
 }
 
-static int old_logger_size (PurpleLog *log)
+static gint
+old_logger_size(PurpleLog *log)
 {
-	struct old_logger_data *data = log->logger_data;
+	old_logger_data *data;
+
+	data = log->logger_data;
+
 	return data ? data->length : 0;
 }
 
-static void old_logger_get_log_sets(PurpleLogSetCallback cb, GHashTable *sets)
+static void
+old_logger_get_log_sets(PurpleLogSetCallback cb, GHashTable *sets)
 {
-	char *log_path = g_build_filename(purple_user_dir(), "logs", NULL);
-	GDir *log_dir = g_dir_open(log_path, 0, NULL);
-	gchar *name;
 	PurpleBlistNode *gnode, *cnode, *bnode;
+	PurpleBuddy *buddy;
+	PurpleLogSet *set;
+	GDir *log_dir;
+	gchar *log_path, *name, *tmp, *ext;
+	size_t len;
+	gboolean found;
 
+	log_path = g_build_filename(purple_user_dir(), "logs", NULL);
+	log_dir = g_dir_open(log_path, 0, NULL);
 	g_free(log_path);
+
 	if (log_dir == 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) {
-		size_t len;
-		gchar *ext;
-		PurpleLogSet *set;
-		gboolean found = FALSE;
+	while ((name = (gchar *) g_dir_read_name(log_dir)) != NULL) {
+		found = FALSE;
 
 		/* Unescape the filename. */
 		name = g_strdup(purple_unescape_filename(name));
@@ -2008,8 +4318,9 @@ static void old_logger_get_log_sets(Purp
 		/* Chat for .chat at the end of the name to determine the type. */
 		*ext = '\0';
 		set->type = PURPLE_LOG_IM;
+
 		if (len > 9) {
-			char *tmp = &name[len - 9];
+			tmp = &name[len - 9];
 			if (purple_strequal(tmp, ".chat")) {
 				set->type = PURPLE_LOG_CHAT;
 				*tmp = '\0';
@@ -2021,23 +4332,20 @@ static void old_logger_get_log_sets(Purp
 		/* Search the buddy list to find the account and to determine if this is a buddy. */
 		for (gnode = purple_blist_get_root();
 		     !found && gnode != NULL;
-			 gnode = purple_blist_node_get_sibling_next(gnode))
-		{
+			 gnode = purple_blist_node_get_sibling_next(gnode)) {
 			if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
 				continue;
 
 			for (cnode = purple_blist_node_get_first_child(gnode);
 			     !found && cnode != NULL;
-				 cnode = purple_blist_node_get_sibling_next(cnode))
-			{
+				 cnode = purple_blist_node_get_sibling_next(cnode)) {
 				if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
 					continue;
 
 				for (bnode = purple_blist_node_get_first_child(cnode);
 				     !found && bnode != NULL;
-					 bnode = purple_blist_node_get_sibling_next(bnode))
-				{
-					PurpleBuddy *buddy = (PurpleBuddy *)bnode;
+					 bnode = purple_blist_node_get_sibling_next(bnode)) {
+					buddy = (PurpleBuddy *) bnode;
 
 					if (purple_strequal(purple_buddy_get_name(buddy), name)) {
 						set->account = purple_buddy_get_account(buddy);
@@ -2048,20 +4356,425 @@ static void old_logger_get_log_sets(Purp
 			}
 		}
 
-		if (!found)
-		{
+		if (!found) {
 			set->account = NULL;
 			set->buddy = FALSE;
 		}
 
 		cb(sets, set);
 	}
+
 	g_dir_close(log_dir);
 }
 
-static void old_logger_finalize(PurpleLog *log)
+static void
+old_logger_finalize(PurpleLog *log)
 {
-	struct old_logger_data *data = log->logger_data;
+	old_logger_data *data;
+
+	data = log->logger_data;
 	purple_stringref_unref(data->pathref);
-	g_slice_free(struct old_logger_data, data);
+	g_slice_free(old_logger_data, data);
 }
+
+static void
+log_delete_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_purple_log_free_callback_data *callback_data = userdata;
+	PurpleLog *log = callback_data->log;
+	GError *err = NULL;
+
+	if (callback_data->cb)
+		callback_data->cb(object, res, callback_data->userdata);
+	else if (!purple_log_delete_finish(log, res, &err) && err->code != G_IO_ERROR_CANCELLED)
+		purple_debug_error("log", "Error freeing log: %s\n", err->message);
+
+	g_clear_error(&err);
+	g_free(callback_data);
+}
+
+static void
+log_free_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	PurpleLog *log = userdata;
+
+	g_free(log->name);
+
+	if (log->tm != NULL) {
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+		/* XXX: This is so wrong... */
+		g_free((gchar *) log->tm->tm_zone);
+#endif
+		g_slice_free(struct tm, log->tm);
+	}
+
+	PURPLE_DBUS_UNREGISTER_POINTER(log);
+	g_slice_free(PurpleLog, log);
+}
+
+static void
+log_list_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_purple_log_logs_callback_data *callback_data = userdata;
+	GSimpleAsyncResult *simple;
+	GList *list, *n;
+
+	list = purple_log_get_logs_finish(res, NULL);
+
+	callback_data->logs = g_list_concat(callback_data->logs, list);
+	callback_data->counter--;
+
+	if (callback_data->counter < 1) {
+		/* Let the caller know we're done. */
+		if (callback_data->cb) {
+			simple = g_simple_async_result_new(object, callback_data->cb,
+				callback_data->userdata, purple_log_common_lister_async);
+
+			g_simple_async_result_set_op_res_gpointer(simple, callback_data->logs, NULL);
+			g_simple_async_result_complete_in_idle(simple);
+
+			g_object_unref(simple);
+		} else {
+			for (n = callback_data->logs; n != NULL; n = g_list_next(n))
+				purple_log_free(n->data);
+
+			g_list_free(callback_data->logs);
+		}
+
+		g_free(callback_data);
+	}
+}
+
+static void
+log_system_list_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_purple_log_logs_callback_data *callback_data = userdata;
+	GSimpleAsyncResult *simple;
+	GList *list, *n;
+
+	list = purple_log_get_logs_finish(res, NULL);
+
+	callback_data->logs = g_list_concat(callback_data->logs, list);
+	callback_data->counter--;
+
+	if (callback_data->counter < 1) {
+		/* Let the caller know we're done. */
+		if (callback_data->cb) {
+			simple = g_simple_async_result_new(object, callback_data->cb,
+				callback_data->userdata, purple_log_common_lister_async);
+
+			g_simple_async_result_set_op_res_gpointer(simple, callback_data->logs, NULL);
+			g_simple_async_result_complete_in_idle(simple);
+
+			g_object_unref(simple);
+		} else {
+			for (n = callback_data->logs; n != NULL; n = g_list_next(n))
+				purple_log_free(n->data);
+
+			g_list_free(callback_data->logs);
+		}
+
+		g_free(callback_data);
+	}
+}
+
+static void
+log_total_size_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_purple_log_total_size_callback_data *callback_data = userdata;
+	GError *err = NULL;
+	gssize size;
+
+	size = purple_log_get_total_size_finish(res, &err);
+
+	/* It's hard to pass all the errors up, so just log them :( */
+	if (size < 0 && err != NULL)
+		purple_debug_error("log", "Error getting total size for logs: %s\n",
+			err->message);
+	else if (size > 0)
+		callback_data->total += size;
+
+	g_clear_error(&err);
+	callback_data->counter--;
+
+	if (callback_data->counter < 1) {
+		if (callback_data->cb) {
+			GSimpleAsyncResult *simple;
+
+			simple = g_simple_async_result_new(NULL, callback_data->cb,
+				callback_data->userdata, purple_log_common_total_sizer_async);
+
+			g_simple_async_result_set_op_res_gssize(simple, callback_data->total);
+			g_simple_async_result_complete_in_idle(simple);
+
+			g_object_unref(simple);
+		}
+
+		if (callback_data->cancellable != NULL)
+			g_object_unref(callback_data->cancellable);
+
+		g_free(callback_data);
+	}
+}
+
+static void
+log_total_size_intermediate_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	GSimpleAsyncResult *simple;
+	GError *err = NULL;
+	gssize size;
+
+	size = purple_log_get_size_finish(object, res, &err);
+
+	simple = g_simple_async_result_new(NULL, log_total_size_cb, userdata,
+		purple_log_common_total_sizer_async);
+
+	if (size < 0)
+		g_simple_async_result_set_from_error(simple, err);
+	else
+		g_simple_async_result_set_op_res_gssize(simple, size);
+
+	g_simple_async_result_complete_in_idle(simple);
+
+	g_object_unref(simple);
+	g_clear_error(&err);
+}
+
+static void
+log_total_size_list_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	/* Gets the size for all the logs in the list as part of purple_log_get_total_size_async */
+	_purple_log_total_size_callback_data *callback_data = userdata;
+	GError *err = NULL;
+	GList *list, *n;
+	GSimpleAsyncResult *simple;
+
+	list = purple_log_get_logs_finish(res, &err);
+
+	if (list == NULL && err != NULL) {
+		simple = g_simple_async_result_new(NULL, log_total_size_cb,
+			callback_data, purple_log_common_total_sizer_async);
+
+		g_simple_async_result_set_from_error(simple, err);
+		g_simple_async_result_complete_in_idle(simple);
+
+		g_object_unref(simple);
+	} else if (g_list_length(list) < 1) {
+		simple = g_simple_async_result_new(NULL, log_total_size_cb,
+			callback_data, purple_log_common_total_sizer_async);
+
+		g_simple_async_result_set_op_res_gssize(simple, 0);
+		g_simple_async_result_complete_in_idle(simple);
+
+		g_object_unref(simple);
+	} else {
+		callback_data->counter--;
+
+		for (n = list; n != NULL; n = g_list_next(n)) {
+			PurpleLog *log = n->data;
+
+			callback_data->counter++;
+
+			purple_log_get_size_async(log, callback_data->io_priority,
+				callback_data->cancellable, log_total_size_cb, callback_data);
+		}
+	}
+
+	g_clear_error(&err);
+}
+
+static void
+log_write_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_purple_log_write_callback_data *callback_data = userdata;
+	_purple_logsize_user *lu = callback_data->lu;
+	PurpleLog *log = callback_data->log;
+	GError *err = NULL;
+	gpointer ptrsize;
+	gsize total = 0;
+	gssize size;
+
+	size = purple_log_write_finish(log, res, &err);
+
+	if (size > 0) {
+		G_LOCK(logsize_users);
+		if (g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize)) {
+			total = GPOINTER_TO_INT(ptrsize);
+			total += size;
+			g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(total));
+		} else {
+			g_free(lu->name);
+			g_free(lu);
+		}
+		G_UNLOCK(logsize_users);
+	}
+
+	if (callback_data->cb) {
+		/* Restore the error in GSimpleAsyncResult as _finish would have cleared it */
+		if (size < 0)
+			g_simple_async_result_set_from_error(G_SIMPLE_ASYNC_RESULT(res), err);
+
+		callback_data->cb(object, res, callback_data->userdata);
+	} else if (size < 0 && err->code != G_IO_ERROR_CANCELLED)
+		purple_debug_error("log", "Error writing message: %s\n", err->message);
+
+	g_clear_error(&err);
+	g_free(callback_data);
+	purple_log_unref(log);
+}
+
+static void
+log_read_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_purple_log_read_callback_data *callback_data = userdata;
+	PurpleLog *log = callback_data->log;
+	GError *err = NULL;
+
+	if (callback_data->cb)
+		callback_data->cb(object, res, callback_data->userdata);
+	else if (purple_log_read_finish(log, res, &err) == NULL  &&
+		err->code != G_IO_ERROR_CANCELLED)
+		purple_debug_error("log", "Error reading log: %s\n", err->message);
+
+	g_clear_error(&err);
+	g_free(callback_data);
+	purple_log_unref(log);
+}
+
+static void
+log_size_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_purple_log_size_callback_data *callback_data = userdata;
+	PurpleLog *log = callback_data->log;
+	GError *err = NULL;
+
+	if (callback_data->cb)
+		callback_data->cb(object, res, callback_data->userdata);
+	else if (purple_log_get_size_finish(log, res, &err) < 0  && err->code != G_IO_ERROR_CANCELLED)
+		purple_debug_error("log", "Error sizing log: %s\n", err->message);
+
+	g_clear_error(&err);
+	g_free(callback_data);
+	purple_log_unref(log);
+}
+
+static void
+log_hash_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_purple_log_sets_callback_data *callback_data = userdata;
+
+	callback_data->counter--;
+
+	if (callback_data->counter < 1) {
+		if (callback_data->cb) {
+			GSimpleAsyncResult *simple;
+
+			simple = g_simple_async_result_new(object, callback_data->cb,
+				callback_data->userdata, log_get_log_sets_common_async);
+
+			g_simple_async_result_set_op_res_gpointer(simple, callback_data->sets,
+				(GDestroyNotify) g_hash_table_unref);
+			g_simple_async_result_complete_in_idle(simple);
+
+			g_object_unref(simple);
+		} else
+			g_hash_table_unref(callback_data->sets);
+
+		g_mutex_free(callback_data->mutex);
+		g_free(callback_data);
+	}
+}
+
+static void
+log_get_activity_score_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_purple_log_activity_score_data *callback_data = userdata;
+	_purple_log_activity_score_size_data *size_data;
+	GSimpleAsyncResult *simple;
+	PurpleLog *log;
+	GError *err = NULL;
+	GList *list;
+
+	list = purple_log_get_logs_finish(res, &err);
+
+	if (list == NULL) {
+		simple = g_simple_async_result_new_from_error(object, callback_data->cb,
+			callback_data->userdata, err);
+
+		g_simple_async_result_complete_in_idle(simple);
+
+		g_object_unref(simple);
+		g_object_unref(callback_data->cancel);
+		g_free(callback_data->lu->name);
+		g_free(callback_data->lu);
+		g_free(callback_data);
+	} else {
+		callback_data->count = g_list_length(list);
+
+		for (callback_data->total = 0.0, callback_data->count = 0; list != NULL;
+			list = g_list_delete_link(list, list))
+		{
+			log = list->data;
+
+			size_data = g_new0(_purple_log_activity_score_size_data, 1);
+			size_data->score_data = callback_data;
+			size_data->log = log;
+
+			purple_log_get_size_async(log, callback_data->io_priority,
+				callback_data->cancel, log_get_activity_score_size_cb, size_data);
+		}
+	}
+
+	g_clear_error(&err);
+}
+
+static void
+log_get_activity_score_size_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_purple_log_activity_score_size_data *callback_data = userdata;
+	_purple_log_activity_score_data *score_data = callback_data->score_data;
+	GSimpleAsyncResult *simple;
+	PurpleLog *log = callback_data->log;
+	GError *err = NULL;
+	time_t now;
+	gssize size;
+	gint score;
+
+	size = purple_log_get_size_finish(log, res, &err);
+
+	if (size < 0 && err->code != G_IO_ERROR_CANCELLED) {
+		purple_debug_error("log", "Error getting log size for activity score: %s\n",
+			err->message);
+	} else if (size > 0) {
+		/* Activity score counts bytes in the log, exponentially
+		   decayed with a half-life of 14 days. */
+		time(&now);
+		score_data->total += size * pow(0.5, difftime(now, log->time) / 1209600.0);
+	}
+
+	g_clear_error(&err);
+	purple_log_free(log);
+	g_free(callback_data);
+
+	score_data->count--;
+
+	if (score_data->count < 1) {
+		score = ceil(score_data->total);
+
+		G_LOCK(logsize_users_decayed);
+		g_hash_table_replace(logsize_users_decayed, score_data->lu,
+			GINT_TO_POINTER(score));
+		G_UNLOCK(logsize_users_decayed);
+
+		simple = g_simple_async_result_new(object, score_data->cb,
+			score_data->userdata, purple_log_get_activity_score_async);
+
+		g_simple_async_result_set_op_res_gssize(simple, score);
+		g_simple_async_result_complete_in_idle(simple);
+
+		g_object_unref(simple);
+		g_object_unref(score_data->cancel);
+		g_free(score_data);
+	}
+}
+
============================================================
--- pidgin/gtkutils.c	5537edee3cffeb641b749752fefd6c80a4ef183a
+++ pidgin/gtkutils.c	37d6539d01772d33c28214a994dceb7a061cad8a
@@ -1899,6 +1899,8 @@ typedef struct
 	gpointer filter_func_user_data;
 
 	GtkListStore *store;
+
+	GCancellable *cancel;
 } PidginCompletionData;
 
 static gboolean buddyname_completion_match_func(GtkEntryCompletion *completion,
@@ -2055,12 +2057,27 @@ static void
 }
 
 static void
+add_completion_list_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	GHashTable *sets;
+	GError *err = NULL;
+
+	sets = purple_log_get_log_sets_finish(res, &err);
+
+	if (sets == NULL && err->code != G_IO_ERROR_CANCELLED)
+		purple_debug_error("gtkutils", "Unable to get hash table: %s\n", err->message);
+	else
+		g_hash_table_foreach(sets, (GHFunc) get_log_set_name, userdata);
+
+	g_clear_error(&err);
+}
+
+static void
 add_completion_list(PidginCompletionData *data)
 {
 	PurpleBlistNode *gnode, *cnode, *bnode;
 	PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
 	gpointer user_data = data->filter_func_user_data;
-	GHashTable *sets;
 
 	gtk_list_store_clear(data->store);
 
@@ -2092,15 +2109,27 @@ add_completion_list(PidginCompletionData
 		}
 	}
 
-	sets = purple_log_get_log_sets();
-	g_hash_table_foreach(sets, (GHFunc)get_log_set_name, data);
-	g_hash_table_destroy(sets);
+	if (data->cancel != NULL) {
+		g_cancellable_cancel(data->cancel);
+		g_object_unref(data->cancel);
+	}
 
+	data->cancel = g_cancellable_new();
+
+	purple_log_get_log_sets_async(G_PRIORITY_DEFAULT, data->cancel,
+		add_completion_list_cb, data);
 }
 
 static void
-buddyname_autocomplete_destroyed_cb(GtkWidget *widget, gpointer data)
+buddyname_autocomplete_destroyed_cb(GtkWidget *widget, gpointer userdata)
 {
+	PidginCompletionData *data = userdata;
+
+	if (data->cancel != NULL) {
+		g_cancellable_cancel(data->cancel);
+		g_object_unref(data->cancel);
+	}
+
 	g_free(data);
 	purple_signals_disconnect_by_handle(widget);
 }
@@ -2139,6 +2168,7 @@ pidgin_setup_screenname_autocomplete_wit
 	}
 	data->store = store;
 
+	data->cancel = NULL;
 	add_completion_list(data);
 
 	/* Sort the completion list by buddy name */
============================================================
--- libpurple/connection.c	e26d259b085aa54418b2ddd5f4e01b1ed087c441
+++ libpurple/connection.c	03b53126a80571d68ad0ae59b93cee09d0617d3e
@@ -46,6 +46,8 @@ static PurpleConnectionUiOps *connection
 static GList *connections_connecting = NULL;
 static PurpleConnectionUiOps *connection_ui_ops = NULL;
 
+static GCancellable *write_conn_cancel = NULL;
+
 static int connections_handle;
 
 static gboolean
@@ -358,10 +360,11 @@ purple_connection_set_state(PurpleConnec
 			{
 				char *msg = g_strdup_printf(_("+++ %s signed on"),
 											purple_account_get_username(account));
-				purple_log_write(log, PURPLE_MESSAGE_SYSTEM,
+				purple_log_write_async(log, PURPLE_MESSAGE_SYSTEM,
 							   purple_account_get_username(account),
 							   purple_presence_get_login_time(presence),
-							   msg);
+							   msg, G_PRIORITY_DEFAULT,
+							   write_conn_cancel, NULL, NULL);
 				g_free(msg);
 			}
 		}
@@ -389,9 +392,10 @@ purple_connection_set_state(PurpleConnec
 			{
 				char *msg = g_strdup_printf(_("+++ %s signed off"),
 											purple_account_get_username(account));
-				purple_log_write(log, PURPLE_MESSAGE_SYSTEM,
+				purple_log_write_async(log, PURPLE_MESSAGE_SYSTEM,
 							   purple_account_get_username(account), time(NULL),
-							   msg);
+							   msg, G_PRIORITY_DEFAULT,
+							   write_conn_cancel, NULL, NULL);
 				g_free(msg);
 			}
 		}
@@ -690,6 +694,8 @@ purple_connections_init(void)
 {
 	void *handle = purple_connections_get_handle();
 
+	write_conn_cancel = g_cancellable_new();
+
 	purple_signal_register(handle, "signing-on",
 						 purple_marshal_VOID__POINTER, NULL, 1,
 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
@@ -727,6 +733,12 @@ purple_connections_uninit(void)
 void
 purple_connections_uninit(void)
 {
+	if (write_conn_cancel != NULL) {
+		g_cancellable_cancel(write_conn_cancel);
+		g_object_unref(write_conn_cancel);
+		write_conn_cancel = NULL;
+	}
+
 	purple_signals_unregister_by_instance(purple_connections_get_handle());
 }
 
============================================================
--- libpurple/log.h	fdae3f0adc203739096c27e7d4bb6fc140b28591
+++ libpurple/log.h	3c17a97c416587ac26aa4e54eddf8355a37f5270
@@ -28,82 +28,123 @@
 #define _PURPLE_LOG_H_
 
 #include <stdio.h>
+#include <glib.h>
+#include <gio/gio.h>
 
+#include "account.h"
+#include "conversation.h"
 
-/********************************************************
- * DATA STRUCTURES **************************************
- ********************************************************/
 
+/** @copydoc _PurpleLog */
 typedef struct _PurpleLog PurpleLog;
+/** @copydoc _PurpleLogLogger */
 typedef struct _PurpleLogLogger PurpleLogLogger;
+/** @copydoc _PurpleLogLoggerAsyncFuncs */
+typedef struct _PurpleLogLoggerAsyncFuncs PurpleLogLoggerAsyncFuncs;
+/** @copydoc _PurpleLogCommonLoggerData */
 typedef struct _PurpleLogCommonLoggerData PurpleLogCommonLoggerData;
+/** @copydoc _PurpleLogSet */
 typedef struct _PurpleLogSet PurpleLogSet;
 
+/**************************************************************************/
+/* Enumerations                                                           */
+/**************************************************************************/
+
+/**
+ * The log type
+ */
 typedef enum {
-	PURPLE_LOG_IM,
-	PURPLE_LOG_CHAT,
-	PURPLE_LOG_SYSTEM
+	PURPLE_LOG_IM,     /**< Chat log with one person */
+	PURPLE_LOG_CHAT,   /**< Group chat log */
+	PURPLE_LOG_SYSTEM  /**< System log */
 } PurpleLogType;
 
+/**
+ * Flags for purple_log_read() and purple_log_read_async()
+ */
 typedef enum {
-	PURPLE_LOG_READ_NO_NEWLINE = 1
+	PURPLE_LOG_READ_NO_NEWLINE = 1     /**< The log contains HTML line break tags, rather than newlines */
 } PurpleLogReadFlags;
 
-#include "account.h"
-#include "conversation.h"
 
 typedef void (*PurpleLogSetCallback) (GHashTable *sets, PurpleLogSet *set);
 
+
+/********************************************************
+ * DATA STRUCTURES **************************************
+ ********************************************************/
+
 /**
+ * A log. Not the wooden type.
+ */
+struct _PurpleLog {
+	PurpleLogType type;        /**< The type of log this is */
+	gchar *name;               /**< The name of this log */
+	PurpleAccount *account;    /**< The account this log is taking place on */
+	PurpleConversation *conv;  /**< The conversation being logged */
+	time_t time;               /**< The time this conversation started, converted to the local timezone */
+	PurpleLogLogger *logger;   /**< The logging mechanism this log is to use */
+	gpointer logger_data;      /**< Data used by the log logger */
+
+	// TODO: On ABI break, it would be really helpful to turn this into a GDateTime!
+	struct tm *tm;             /**< The time this conversation started, saved with original timezone data,
+								*   if available and if struct tm has the BSD timezone fields, else @c %NULL.
+								*   Do NOT modify anything in this struct.
+								*/
+
+	/* IMPORTANT: Some code in log.c allocates these without zeroing them.
+	 * IMPORTANT: Update that code if you add members here. */
+};
+
+/**
  * A log logger.
  *
  * This struct gets filled out and is included in the PurpleLog.  It contains everything
  * needed to write and read from logs.
  */
 struct _PurpleLogLogger {
-	char *name;               /**< The logger's name */
-	char *id;                 /**< an identifier to refer to this logger */
+	gchar *name;       /**< The logger's name */
+	gchar *id;         /**< An identifier to refer to this logger */
 
-	/** This gets called when the log is first created.
-	    I don't think this is actually needed. */
+	/** This gets called when the log is first created, likely not actually needed */
 	void (*create)(PurpleLog *log);
 
 	/** This is used to write to the log file */
 	gsize (*write)(PurpleLog *log,
 		     PurpleMessageFlags type,
-		     const char *from,
+		     const gchar *from,
 		     time_t time,
-		     const char *message);
+		     const gchar *message);
 
 	/** Called when the log is destroyed */
 	void (*finalize)(PurpleLog *log);
 
-	/** This function returns a sorted GList of available PurpleLogs */
-	GList *(*list)(PurpleLogType type, const char *name, PurpleAccount *account);
+	/** This function returns a sorted #GList of available #PurpleLog<!-- -->s */
+	GList *(*list)(PurpleLogType type, const gchar *name, PurpleAccount *account);
 
 	/** Given one of the logs returned by the logger's list function,
 	 *  this returns the contents of the log in GtkIMHtml markup */
-	char *(*read)(PurpleLog *log, PurpleLogReadFlags *flags);
+	gchar *(*read)(PurpleLog *log, PurpleLogReadFlags *flags);
 
 	/** Given one of the logs returned by the logger's list function,
 	 *  this returns the size of the log in bytes */
-	int (*size)(PurpleLog *log);
+	gint (*size)(PurpleLog *log);
 
 	/** Returns the total size of all the logs. If this is undefined a default
 	 *  implementation is used */
-	int (*total_size)(PurpleLogType type, const char *name, PurpleAccount *account);
+	gint (*total_size)(PurpleLogType type, const gchar *name, PurpleAccount *account);
 
-	/** This function returns a sorted GList of available system PurpleLogs */
+	/** This function returns a sorted #GList of available system #PurpleLog<!-- -->s */
 	GList *(*list_syslog)(PurpleAccount *account);
 
-	/** Adds PurpleLogSets to a GHashTable. By passing the data in the PurpleLogSets
+	/** Adds #PurpleLogSet<!-- -->s to a #GHashTable. By passing the data in the #PurpleLogSet<!-- -->s
 	 *  to list, the caller can get every available PurpleLog from the logger.
 	 *  Loggers using purple_log_common_writer() (or otherwise storing their
 	 *  logs in the same directory structure as the stock loggers) do not
 	 *  need to implement this function.
 	 *
-	 *  Loggers which implement this function must create a PurpleLogSet,
-	 *  then call @a cb with @a sets and the newly created PurpleLogSet. */
+	 *  Loggers which implement this function must create a #PurpleLogSet,
+	 *  then call @a cb with @a sets and the newly created #PurpleLogSet. */
 	void (*get_log_sets)(PurpleLogSetCallback cb, GHashTable *sets);
 
 	/* Attempts to delete the specified log, indicating success or failure */
@@ -112,36 +153,70 @@ struct _PurpleLogLogger {
 	/* Tests whether a log is deletable */
 	gboolean (*is_deletable)(PurpleLog *log);
 
-	void (*_purple_reserved1)(void);
+	/** A pointer to a #PurpleLogLoggerAsyncFuncs structure containing the non-blocking
+	 *  versions of the calls above */
+	// Note: This will only be used up until the 3.x API break, when we will create
+	// threads that directly call the blocking methods, which will be altered to accept
+	// cancellable/error objects
+	PurpleLogLoggerAsyncFuncs *async;
+
 	void (*_purple_reserved2)(void);
 	void (*_purple_reserved3)(void);
 	void (*_purple_reserved4)(void);
 };
 
 /**
- * A log.  Not the wooden type.
+ * This struct contains the non-blocking versions of the #PurpleLogLogger functions
  */
-struct _PurpleLog {
-	PurpleLogType type;                     /**< The type of log this is */
-	char *name;                           /**< The name of this log */
-	PurpleAccount *account;                 /**< The account this log is taking
-	                                           place on */
-	PurpleConversation *conv;               /**< The conversation being logged */
-	time_t time;                          /**< The time this conversation
-	                                           started, converted to the local timezone */
+struct _PurpleLogLoggerAsyncFuncs {
+	// TODO: We could probably drop this. There's really no reason to use it.
+	// TODO: If we do, we should deprecate create at the same time.
+	/** Non-blocking version of create */
+	void (*create_async)(PurpleLog *log, gint io_priority, GCancellable *cancellable,
+		GAsyncReadyCallback cb, gpointer data);
 
-	PurpleLogLogger *logger;                /**< The logging mechanism this log
-	                                           is to use */
-	void *logger_data;                    /**< Data used by the log logger */
-	struct tm *tm;                        /**< The time this conversation
-	                                           started, saved with original
-	                                           timezone data, if available and
-	                                           if struct tm has the BSD
-	                                           timezone fields, else @c NULL.
-	                                           Do NOT modify anything in this struct.*/
+	/** Non-blocking version of write */
+	void (*write_async)(PurpleLog *log, PurpleMessageFlags type, const gchar *from,
+		time_t time, const gchar *message, gint io_priority, GCancellable *cancellable,
+		GAsyncReadyCallback cb, gpointer data);
 
-	/* IMPORTANT: Some code in log.c allocates these without zeroing them.
-	 * IMPORTANT: Update that code if you add members here. */
+	/** Non-blocking version of finalize */
+	void (*finalize_async)(PurpleLog *log, gint io_priority, GCancellable *cancellable,
+		GAsyncReadyCallback cb, gpointer data);
+
+	/** Non-blocking version of list */
+	void (*list_async)(PurpleLogType type, const gchar *name, PurpleAccount *account,
+		gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb, gpointer data);
+
+	/** Non-blocking version of read */
+	void (*read_async)(PurpleLog *log, PurpleLogReadFlags *flags, gint io_priority,
+		GCancellable *cancellable, GAsyncReadyCallback cb, gpointer data);
+
+	/** Non-blocking version of size */
+	void (*size_async)(PurpleLog *log, gint io_priority, GCancellable *cancellable,
+		GAsyncReadyCallback cb, gpointer data);
+
+	/** Non-blocking version of total_size */
+	void (*total_size_async)(PurpleLogType type, const gchar *name, PurpleAccount *account,
+		gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb, gpointer data);
+
+	/** Non-blocking version of list_syslog */
+	void (*list_syslog_async)(PurpleAccount *account, gint io_priority,
+		GCancellable *cancellable, GAsyncReadyCallback cb, gpointer data);
+
+	/** Non-blocking version of get_log_sets */
+	void (*get_log_sets_async)(PurpleLogSetCallback set_cb, GHashTable *sets,
+		gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb, gpointer data);
+
+	/** Non-blocking version of remove */
+	void (*remove_async)(PurpleLog *log, gint io_priority, GCancellable *cancellable,
+		GAsyncReadyCallback cb, gpointer data);
+
+
+	void (*_purple_reserved1)(void);
+	void (*_purple_reserved2)(void);
+	void (*_purple_reserved3)(void);
+	void (*_purple_reserved4)(void);
 };
 
 /**
@@ -149,67 +224,62 @@ struct _PurpleLogCommonLoggerData {
  * as a pointer to something else for additional data.
  */
 struct _PurpleLogCommonLoggerData {
-	char *path;
-	FILE *file;
-	void *extra_data;
+	gchar *path;         /**< Path to log */
+	FILE *file;          /**< Log's file handle */
+	gpointer extra_data; /**< Any extra data passed */
 };
 
 /**
  * Describes available logs.
  *
  * By passing the elements of this struct to purple_log_get_logs(), the caller
- * can get all available PurpleLogs.
+ * can get all available #PurpleLog<!-- -->s.
  */
 struct _PurpleLogSet {
-	PurpleLogType type;                     /**< The type of logs available */
-	char *name;                           /**< The name of the logs available */
-	PurpleAccount *account;                 /**< The account the available logs
-	                                           took place on. This will be
-	                                           @c NULL if the account no longer
-	                                           exists. (Depending on a
-	                                           logger's implementation of
-	                                           list, it may not be possible
-	                                           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. */
+	PurpleLogType type;        /**< The type of logs available */
+	gchar *name;               /**< The name of the logs available */
+	PurpleAccount *account;    /**< The account the available logs took place on.
+								*   This will be @c %NULL if the account no longer exists.
+								*
+								*   (Depending on a logger's implementation of list, it may
+								*   not be possible to load such logs.)
+								*/
+	gboolean buddy;            /**< Is this (account, name) a buddy on the buddy list? */
+	gchar *normalized_name;    /**< The normalized version of @a name. It must be set, and
+								*   may be set to the same pointer value as @a name. */
 
 	/* IMPORTANT: Some code in log.c allocates these without zeroing them.
 	 * IMPORTANT: Update that code if you add members here. */
 };
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+G_BEGIN_DECLS
 
 /***************************************/
 /** @name Log Functions                */
 /***************************************/
+
 /*@{*/
 
 /**
  * Creates a new log
  *
- * @param type        The type of log this is.
- * @param name        The name of this conversation (buddy name, chat name,
- *                    etc.)
- * @param account     The account the conversation is occurring on
- * @param conv        The conversation being logged
- * @param time        The time this conversation started
- * @param tm          The time this conversation started, with timezone data,
- *                    if available and if struct tm has the BSD timezone fields.
- * @return            The new log
+ * @param type         The type of log this is
+ * @param name         The name of this conversation (buddy name, chat name, etc.)
+ * @param account      The account the conversation is occurring on
+ * @param conv         (allow-none): The conversation being logged
+ * @param time         The time this conversation started
+ * @param tm           (allow-none): The time this conversation started, with timezone data,
+ *                     if available and if struct tm has the BSD timezone fields
+ *
+ * @return             The new log
  */
-PurpleLog *purple_log_new(PurpleLogType type, const char *name, PurpleAccount *account,
-                      PurpleConversation *conv, time_t time, const struct tm *tm);
+PurpleLog *purple_log_new(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	PurpleConversation *conv, time_t time, const struct tm *tm);
 
 /**
  * Frees a log
  *
- * @param log         The log to destroy
+ * @param log          The log to destroy
  */
 void purple_log_free(PurpleLog *log);
 
@@ -223,143 +293,410 @@ void purple_log_free(PurpleLog *log);
  * @param time         A timestamp in UNIX time
  * @param message      The message to log
  */
-void purple_log_write(PurpleLog *log,
-		    PurpleMessageFlags type,
-		    const char *from,
-		    time_t time,
-		    const char *message);
+void purple_log_write(PurpleLog *log, PurpleMessageFlags type, const gchar *from,
+	time_t time, const gchar *message);
 
 /**
+ * Writes to a log file asychronously. Assumes you have checked preferences already.
+ *
+ * @param log          The log to write to
+ * @param type         The type of message being logged
+ * @param from         Whom this message is coming from, or @c %NULL for system messages
+ * @param time         A timestamp in UNIX time
+ * @param message      The message to log (will be deleted in function)
+ * @param io_priority  The io priority of the request
+ * @param cancellable  (allow-none): #GCancellable object
+ * @param cb           (allow-none): A #GAsyncReadyCallback to call when the request is satisfied
+ * @param userdata     (allow-none): The data to pass to callback function
+ *
+ * @since 2.8.0
+ */
+void purple_log_write_async(PurpleLog *log, PurpleMessageFlags type, const gchar *from, time_t time,
+	const gchar *message, gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb,
+	gpointer userdata);
+
+/**
+ * Finishing asynchronously writing to a log
+ *
+ * @param log          The #PurpleLog that was written to
+ * @param res          A #GAsyncResult
+ * @param error        (out) (allow-none): a #GError location to store the error
+ *
+ * @return             Number of bytes written, -1 on failure
+ *
+ * @since 2.8.0
+ */
+gssize purple_log_write_finish(PurpleLog *log, GAsyncResult *res, GError **error);
+
+/**
  * Reads from a log
  *
- * @param log   The log to read from
- * @param flags The returned logging flags.
+ * @param log          The log to read from
+ * @param flags        (allow-none): Location to store the returned logging flags
  *
- * @return The contents of this log in Purple Markup.
+ * @return             The contents of this log in Purple Markup
  */
-char *purple_log_read(PurpleLog *log, PurpleLogReadFlags *flags);
+gchar *purple_log_read(PurpleLog *log, PurpleLogReadFlags *flags);
 
 /**
+ * Reads from a log asynchronously
+ *
+ * @param log          The log to read from
+ * @param flags        (allow-none): Location to store the returned logging flags
+ * @param io_priority  The io priority of the request
+ * @param cancellable  (allow-none): #GCancellable object
+ * @param cb           (allow-none): A #GAsyncReadyCallback to call when the request is satisfied
+ * @param userdata     (allow-none): The data to pass to callback function
+ *
+ * @since 2.8.0
+ */
+void purple_log_read_async(PurpleLog *log, PurpleLogReadFlags *flags, gint io_priority,
+	GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata);
+
+/**
+ * Finishes asynchronously reading a log
+ *
+ * Note: If error's message is set, it will already contain the HTML formatting tags
+ *
+ * @param log          The #PurpleLog that was read
+ * @param res          A #GAsyncResult
+ * @param error        (out) (allow-none): a #GError location to store the error
+ *
+ * @return pointer to contents of file or %NULL on error
+ *
+ * @since 2.8.0
+ */
+gchar *purple_log_read_finish(PurpleLog *log, GAsyncResult *res, GError **error);
+
+/**
  * Returns a list of all available logs
  *
- * @param type                The type of the log
- * @param name                The name of the log
- * @param account             The account
- * @return                    A sorted list of PurpleLogs
+ * @param type         The type of the log
+ * @param name         The name of the log
+ * @param account      The account
+ *
+ * @return             A sorted list of #PurpleLog<!-- -->s
  */
-GList *purple_log_get_logs(PurpleLogType type, const char *name, PurpleAccount *account);
+GList *purple_log_get_logs(PurpleLogType type, const gchar *name, PurpleAccount *account);
 
 /**
- * Returns a GHashTable of PurpleLogSets.
+ * Asynchronously gets a list of all available logs
  *
+ * @param type         The type of the log
+ * @param name         The name of the log
+ * @param account      The account
+ * @param io_priority  The io priority of the request
+ * @param cancellable  (allow-none): #GCancellable object
+ * @param cb           (allow-none): A #GAsyncReadyCallback to call when the request is satisfied
+ * @param userdata     (allow-none): The data to pass to callback function
+ *
+ * @since 2.8.0
+ */
+void purple_log_get_logs_async(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata);
+
+/**
+ * Finishes asynchronously getting a list of all available logs
+ *
+ * Note: Make sure to examine that error is not NULL as it is completely possible
+ * for the list to be empty and the function to return NULL without an error
+ *
+ * @param res          A #GAsyncResult
+ * @param error        (out) (allow-none): a #GError location to store the error
+ *
+ * @return             pointer to log list or %NULL on error
+ *
+ * @since 2.8.0
+ */
+GList *purple_log_get_logs_finish(GAsyncResult *res, GError **error);
+
+/**
+ * Returns a GHashTable of #PurpleLogSet<!-- -->s.
+ *
  * A "log set" here means the information necessary to gather the
- * PurpleLogs for a given buddy/chat. This information would be passed
- * to purple_log_list to get a list of PurpleLogs.
+ * #PurpleLog<!-- -->s for a given buddy/chat. This information would be passed
+ * to purple_log_get_log to get a list of #PurpleLog<!-- -->s.
  *
  * 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).
  *
  * The GHashTable that's returned will free all log sets in it when
- * destroyed. If a PurpleLogSet is removed from the GHashTable, it
+ * destroyed. If a #PurpleLogSet is removed from the GHashTable, it
  * must be freed with purple_log_set_free().
  *
- * @return A GHashTable of all available unique PurpleLogSets
+ * @return             A GHashTable of all available unique #PurpleLogSet<!-- -->s
  */
 GHashTable *purple_log_get_log_sets(void);
 
 /**
+ * Asychronously gets a #GHashTable of #PurpleLogSet<!-- -->s.
+ *
+ * For more details, see purple_log_get_log_sets() which is
+ * the synchronous version of this call.
+ *
+ * @param io_priority  The io priority of the request
+ * @param cancellable  (allow-none): #GCancellable object
+ * @param cb           (allow-none): A #GAsyncReadyCallback to call when the request is satisfied
+ * @param userdata     (allow-none): The data to pass to callback function
+ *
+ * @since 2.8.0
+ */
+void purple_log_get_log_sets_async(gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata);
+
+/**
+ * Finishes asynchronously getting a hash table of log sets
+ *
+ * @param res          A #GAsyncResult
+ * @param error        (out) (allow-none): a #GError location to store the error
+ *
+ * @return             pointer to hash table of log sets or %NULL on error
+ *
+ * @since 2.8.0
+ */
+GHashTable *purple_log_get_log_sets_finish(GAsyncResult *res, GError **error);
+
+/**
  * Returns a list of all available system logs
  *
- * @param account The account
- * @return        A sorted list of PurpleLogs
+ * @param account      The account
+ *
+ * @return             A sorted list of #PurpleLog<!-- -->s
  */
 GList *purple_log_get_system_logs(PurpleAccount *account);
 
 /**
+ * Asychronously gets a list of all available system logs
+ *
+ * @param account      The account
+ * @param io_priority  The io priority of the request
+ * @param cancellable  (allow-none): #GCancellable object
+ * @param cb           (allow-none): A #GAsyncReadyCallback to call when the request is satisfied
+ * @param userdata     (allow-none): The data to pass to callback function
+ *
+ * @since 2.8.0
+ */
+void purple_log_get_system_logs_async(PurpleAccount *account, gint io_priority,
+	GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata);
+
+/**
+ * Finishes asychronously getting a list of all available system logs
+ *
+ * Note: Make sure to examine that error is not NULL as it is completely possible
+ * for the list to be empty and the function to return NULL without an error
+ *
+ * @param res          A #GAsyncResult
+ * @param error        (out) (allow-none): a #GError location to store the error
+ *
+ * @return             pointer to log list or %NULL on error
+ *
+ * @since 2.8.0
+ */
+GList *purple_log_get_system_logs_finish(GAsyncResult *res, GError **error);
+
+/**
  * Returns the size of a log
  *
- * @param log                 The log
- * @return                    The size of the log, in bytes
+ * @param log          The log
+ *
+ * @return             The size of the log, in bytes
  */
-int purple_log_get_size(PurpleLog *log);
+gint purple_log_get_size(PurpleLog *log);
 
 /**
+ * Asychronously gets the size of a log
+ *
+ * @param log          The log
+ * @param io_priority  The io priority of the request
+ * @param cancellable  (allow-none): #GCancellable object
+ * @param cb           (allow-none): A #GAsyncReadyCallback to call when the request is satisfied
+ * @param userdata     (allow-none): The data to pass to callback function
+ *
+ * @since 2.8.0
+ */
+void purple_log_get_size_async(PurpleLog *log, gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata);
+
+/**
+ * Finishes asynchronously getting the size of a log
+ *
+ * @param log          The #PurpleLog to size
+ * @param res          A #GAsyncResult
+ * @param error        (out) (allow-none): a #GError location to store the error
+ *
+ * @return             size of file or -1 on error
+ *
+ * @since 2.8.0
+ */
+gssize purple_log_get_size_finish(PurpleLog *log, GAsyncResult *res, GError **error);
+
+/**
  * Returns the size, in bytes, of all available logs in this conversation
  *
- * @param type                The type of the log
- * @param name                The name of the log
- * @param account             The account
- * @return                    The size in bytes
+ * @param type         The type of the log
+ * @param name         The name of the log
+ * @param account      The account
+ *
+ * @return             The size in bytes
  */
-int purple_log_get_total_size(PurpleLogType type, const char *name, PurpleAccount *account);
+gint purple_log_get_total_size(PurpleLogType type, const gchar *name, PurpleAccount *account);
 
 /**
+ * Asychronously gets the size, in bytes, of all available logs in this conversation
+ *
+ * @param type         The type of the log
+ * @param name         The name of the log
+ * @param account      The account
+ * @param io_priority  The io priority of the request
+ * @param cancellable  (allow-none): #GCancellable object
+ * @param cb           (allow-none): A #GAsyncReadyCallback to call when the request is satisfied
+ * @param userdata     (allow-none): The data to pass to callback function
+ *
+ * @since 2.8.0
+ */
+void purple_log_get_total_size_async(PurpleLogType type, const gchar *name, PurpleAccount *account, 
+	gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata);
+
+/**
+ * Finishes asynchronously getting the total size of a conversation
+ *
+ * @param res          A #GAsyncResult
+ * @param error        (out) (allow-none): a #GError location to store the error
+ *
+ * @return             size of file or -1 on error
+ *
+ * @since 2.8.0
+ */
+gssize purple_log_get_total_size_finish(GAsyncResult *res, GError **error);
+
+/**
  * Returns the activity score of a log, based on total size in bytes,
  * which is then decayed based on age
  *
- * @param type                The type of the log
- * @param name                The name of the log
- * @param account             The account
- * @return                    The activity score
+ * @param type         The type of the log
+ * @param name         The name of the log
+ * @param account      The account
  *
+ * @return             The activity score
+ *
  * @since 2.6.0
  */
-int purple_log_get_activity_score(PurpleLogType type, const char *name, PurpleAccount *account);
+gint purple_log_get_activity_score(PurpleLogType type, const gchar *name, PurpleAccount *account);
 
 /**
+ * Asychronously gets the activity score of a log
+ *
+ * @param type         The type of the log
+ * @param name         The name of the log
+ * @param account      The account
+ * @param io_priority  The io priority of the request
+ * @param cancellable  (allow-none): #GCancellable object
+ * @param cb           (allow-none): A #GAsyncReadyCallback to call when the request is satisfied
+ * @param userdata     (allow-none): The data to pass to callback function
+ *
+ * @since 2.8.0
+ */
+void purple_log_get_activity_score_async(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	gint io_priority, GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata);
+
+/**
+ * Finishes asynchronously getting the activity score of a log
+ *
+ * @param res          A #GAsyncResult
+ * @param error        (out) (allow-none): a #GError location to store the error
+ *
+ * @return             The activity score of the log or -1 on failure
+ *
+ * @since 2.8.0
+ */
+gint purple_log_get_activity_score_finish(GAsyncResult *res, GError **error);
+
+/**
  * Tests whether a log is deletable
  *
  * A return value of @c FALSE indicates that purple_log_delete() will fail on this
  * log, unless something changes between the two calls.  A return value of @c TRUE,
  * however, does not guarantee the log can be deleted.
  *
- * @param log                 The log
- * @return                    A boolean indicating if the log is deletable
+ * @param log          The log
+ *
+ * @return             %TRUE if the log is deletable, %FALSE otherwise
  */
 gboolean purple_log_is_deletable(PurpleLog *log);
 
 /**
  * Deletes a log
  *
- * @param log                 The log
- * @return                    A boolean indicating success or failure
+ * @param log          The log
+ *
+ * @return             %TRUE if the log was deleted, %FALSE otherwise
  */
 gboolean purple_log_delete(PurpleLog *log);
 
 /**
+ * Asychronously deletes a log
+ *
+ * @param log          The log
+ * @param io_priority  The io priority of the request
+ * @param cancellable  (allow-none): #GCancellable object
+ * @param cb           (allow-none): A #GAsyncReadyCallback to call when the request is satisfied
+ * @param userdata     (allow-none): The data to pass to callback function
+ *
+ * @since 2.8.0
+ */
+void purple_log_delete_async(PurpleLog *log, gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata);
+
+/**
+ * Finishes asynchronously deleting a log
+ *
+ * @param log          The #PurpleLog that was to be deleted
+ * @param res          A #GAsyncResult
+ * @param error        (out) (allow-none): a #GError location to store the error
+ *
+ * @return             %TRUE if the log was deleted, %FALSE otherwise
+ *
+ * @since 2.8.0
+ */
+gboolean purple_log_delete_finish(PurpleLog *log, GAsyncResult *res, GError **error);
+
+/**
  * Returns the default logger directory Purple uses for a given account
  * and username.  This would be where Purple stores logs created by
  * the built-in text or HTML loggers.
  *
- * @param type                The type of the log.
- * @param name                The name of the log.
- * @param account             The account.
- * @return                    The default logger directory for Purple.
+ * @param type         The type of the log
+ * @param name         The name of the log
+ * @param account      The account
+ *
+ * @return             The default logger directory for Purple
  */
-char *purple_log_get_log_dir(PurpleLogType type, const char *name, PurpleAccount *account);
+gchar *purple_log_get_log_dir(PurpleLogType type, const gchar *name, PurpleAccount *account);
 
 /**
- * Implements GCompareFunc for PurpleLogs
+ * Implements GCompareFunc for #PurpleLog<!-- -->s
  *
- * @param y                   A PurpleLog
- * @param z                   Another PurpleLog
- * @return                    A value as specified by GCompareFunc
+ * @param y            A #PurpleLog
+ * @param z            Another #PurpleLog
+ *
+ * @return             A value as specified by GCompareFunc
  */
 gint purple_log_compare(gconstpointer y, gconstpointer z);
 
 /**
- * Implements GCompareFunc for PurpleLogSets
+ * Implements GCompareFunc for #PurpleLogSet<!-- -->s
  *
- * @param y                   A PurpleLogSet
- * @param z                   Another PurpleLogSet
- * @return                    A value as specified by GCompareFunc
+ * @param y            A #PurpleLogSet
+ * @param z            Another #PurpleLogSet
+ *
+ * @return             A value as specified by GCompareFunc
  */
 gint purple_log_set_compare(gconstpointer y, gconstpointer z);
 
 /**
  * Frees a log set
  *
- * @param set         The log set to destroy
+ * @param set          The log set to destroy
  */
 void purple_log_set_free(PurpleLogSet *set);
 
@@ -368,10 +705,11 @@ void purple_log_set_free(PurpleLogSet *s
 /******************************************/
 /** @name Common Logger Functions         */
 /******************************************/
+
 /*@{*/
 
 /**
- * Opens a new log file in the standard Purple log location
+ * Opens a new log file in the standard log location
  * with the given file extension, named for the current time,
  * for writing.  If a log file is already open, the existing
  * file handle is retained.  The log's logger_data value is
@@ -383,33 +721,100 @@ void purple_log_set_free(PurpleLogSet *s
  * It should only be passed to purple_log_logger_new() and never
  * called directly.
  *
- * @param log   The log to write to.
- * @param ext   The file extension to give to this log file.
+ * @param log          The log to write to
+ * @param ext          (allow-none): The file extension to give to this log file
  */
-void purple_log_common_writer(PurpleLog *log, const char *ext);
+void purple_log_common_writer(PurpleLog *log, const gchar *ext);
 
 /**
- * Returns a sorted GList of PurpleLogs of the requested type.
+ * Asynchronously opens a new log in the standard log location
  *
+ * For more details, see purple_log_common_writer() which is
+ * the synchronous version of this call.
+ *
+ * @param log          The log to write to
+ * @param ext          (allow-none): The file extension to give to this log file
+ * @param io_priority  The io priority of the request
+ * @param cancellable  (allow-none): #GCancellable object
+ * @param cb           (allow-none): A #GAsyncReadyCallback to call when the request is satisfied
+ * @param userdata     (allow-none): The data to pass to callback function
+ */
+void purple_log_common_writer_async(PurpleLog *log, const gchar *ext, gint io_priority,
+	GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata);
+
+/**
+ * Asynchronously opens a new log in the standard log location
+ *
+ * For more details, see purple_log_common_writer() which is
+ * the synchronous version of this call.
+ *
+ * @param log          The log to write to
+ * @param res          A #GAsyncResult
+ * @param error        (out) (allow-none): a #GError location to store the error
+ *
+ * @return             %TRUE if the log was created, %FALSE otherwise
+ */
+gboolean purple_log_common_writer_finish(PurpleLog *log, GAsyncResult *res, GError **error);
+
+/**
+ * Returns a sorted #GList of #PurpleLog<!-- -->s of the requested type
+ *
  * This function should only be used with logs that are written
  * with purple_log_common_writer().  It's intended to be used as
  * a "common" implementation of a logger's @c list function.
  * It should only be passed to purple_log_logger_new() and never
  * called directly.
  *
- * @param type     The type of the logs being listed.
- * @param name     The name of the log.
- * @param account  The account of the log.
- * @param ext      The file extension this log format uses.
- * @param logger   A reference to the logger struct for this log.
+ * @param type         The type of the logs being listed
+ * @param name         The name of the log
+ * @param account      The account of the log
+ * @param ext          The file extension this log format uses
+ * @param logger       A reference to the logger struct for this log
  *
- * @return A sorted GList of PurpleLogs matching the parameters.
+ * @return             A sorted #GList of #PurpleLog<!-- -->s matching the parameters
  */
-GList *purple_log_common_lister(PurpleLogType type, const char *name,
-							  PurpleAccount *account, const char *ext,
-							  PurpleLogLogger *logger);
+GList *purple_log_common_lister(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	const gchar *ext, PurpleLogLogger *logger);
 
 /**
+ * Asynchronously gets a sorted #GList of #PurpleLog<!-- -->s of the requested type
+ *
+ * For more details, see purple_log_common_lister() which is
+ * the synchronous version of this call.
+ *
+ * @param type         The type of the logs being listed
+ * @param name         The name of the log
+ * @param account      The account of the log
+ * @param ext          The file extension this log format uses
+ * @param logger       A reference to the logger struct for this log
+ * @param io_priority  The io priority of the request
+ * @param cancellable  (allow-none): #GCancellable object
+ * @param cb           (allow-none): A #GAsyncReadyCallback to call when the request is satisfied
+ * @param userdata     (allow-none): The data to pass to callback function
+ *
+ * @since 2.8.0
+ */
+void purple_log_common_lister_async(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	const gchar *ext, PurpleLogLogger *logger, gint io_priority,
+	GCancellable *cancellable, GAsyncReadyCallback cb, gpointer userdata);
+
+/**
+ * Finishes asynchronously lister listing the #PurpleLog<!-- -->s of a requested type.
+ * Remember to free the #GList returned if you do not store it.
+ *
+ * Note: Make sure to examine that error is not NULL as it is completely possible
+ * for the list to be empty and the function to return NULL without an error
+ *
+ * @param res          A #GAsyncResult
+ * @param error        (out) (allow-none): a #GError location to store the error
+ *
+ * @return             A sorted #GList of #PurpleLog<!-- -->s or %NULL on error
+ *
+ * @since 2.8.0
+ */
+GList *purple_log_common_lister_finish(GAsyncResult *res, GError **error);
+
+/**
  * Returns the total size of all the logs for a given user, with
  * a given extension.
  *
@@ -419,19 +824,53 @@ GList *purple_log_common_lister(PurpleLo
  * It should only be passed to purple_log_logger_new() and never
  * called directly.
  *
- * @param type     The type of the logs being sized.
- * @param name     The name of the logs to size
- *                 (e.g. the username or chat name).
- * @param account  The account of the log.
- * @param ext      The file extension this log format uses.
+ * @param type         The type of the logs being sized
+ * @param name         The name of the logs to size
+ *                     (e.g. the username or chat name)
+ * @param account      The account of the log
+ * @param ext          The file extension this log format uses
  *
- * @return The size of all the logs with the specified extension
- *         for the specified user.
+ * @return             The size of all the logs with the specified extension
+ *                     for the specified user
  */
-int purple_log_common_total_sizer(PurpleLogType type, const char *name,
-								PurpleAccount *account, const char *ext);
+gint purple_log_common_total_sizer(PurpleLogType type, const gchar *name, PurpleAccount *account,
+	const gchar *ext);
 
 /**
+ * Asychronously gets the total size of all the logs for a given user, with
+ * a given extension.
+ *
+ * For more details, see purple_log_common_total_sizer() which is
+ * the synchronous version of this call.
+ *
+ * @param type         The type of the logs being sized
+ * @param name         The name of the logs to size (e.g. the username or chat name)
+ * @param account      The account of the log
+ * @param ext          The file extension this log format uses
+ * @param io_priority  The io priority of the request
+ * @param cancellable  (allow-none): #GCancellable object
+ * @param cb           (allow-none): A #GAsyncReadyCallback to call when the request is satisfied
+ * @param userdata     (allow-none): The data to pass to callback function
+ *
+ * @since 2.8.0
+ */
+void purple_log_common_total_sizer_async(PurpleLogType type, const gchar *name,
+	PurpleAccount *account, const gchar *ext, gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata);
+
+/**
+ * Finishes asynchronously getting the total size of logs for a given user
+ *
+ * @param res          A #GAsyncResult
+ * @param error        (out) (allow-none): a #GError location to store the error
+ *
+ * @return             size of file or -1 on error
+ *
+ * @since 2.8.0
+ */
+gssize purple_log_common_total_sizer_finish(GAsyncResult *res, GError **error);
+
+/**
  * Returns the size of a given PurpleLog.
  *
  * This function should only be used with logs that are written
@@ -440,13 +879,43 @@ int purple_log_common_total_sizer(Purple
  * It should only be passed to purple_log_logger_new() and never
  * called directly.
  *
- * @param log      The PurpleLog to size.
+ * @param log          The #PurpleLog to size
  *
- * @return An integer indicating the size of the log in bytes.
+ * @return             An integer indicating the size of the log in bytes
  */
-int purple_log_common_sizer(PurpleLog *log);
+gint purple_log_common_sizer(PurpleLog *log);
 
 /**
+ * Asychronously gets the size of a given #PurpleLog
+ *
+ * For more details, see purple_log_common_sizer() which is
+ * the synchronous version of this call.
+ *
+ * @param log          The #PurpleLog to size
+ * @param io_priority  The io priority of the request
+ * @param cancellable  (allow-none): #GCancellable object
+ * @param cb           (allow-none): A #GAsyncReadyCallback to call when the request is satisfied
+ * @param userdata     (allow-none): The data to pass to callback function
+ *
+ * @since 2.8.0
+ */
+void purple_log_common_sizer_async(PurpleLog *log, gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata);
+
+/**
+ * Finishes asynchronously getting the size of a log
+ *
+ * @param log          The #PurpleLog to size
+ * @param res          A #GAsyncResult
+ * @param error        (out) (allow-none): a #GError location to store the error
+ *
+ * @return             size of file or -1 on error
+ *
+ * @since 2.8.0
+ */
+gssize purple_log_common_sizer_finish(PurpleLog *log, GAsyncResult *res, GError **error);
+
+/**
  * Deletes a log
  *
  * This function should only be used with logs that are written
@@ -455,13 +924,46 @@ int purple_log_common_sizer(PurpleLog *l
  * It should only be passed to purple_log_logger_new() and never
  * called directly.
  *
- * @param log      The PurpleLog to delete.
+ * @param log          The #PurpleLog to delete
  *
- * @return A boolean indicating success or failure.
+ * @return             %TRUE if the log was deleted, %FALSE otherwise
  */
 gboolean purple_log_common_deleter(PurpleLog *log);
 
 /**
+ * Asynchronously deletes a log
+ *
+ * This function should only be used with logs that are written
+ * with purple_log_common_writer().  It's intended to be used as
+ * a "common" implementation of a logger's @c delete function.
+ * It should only be passed to purple_log_logger_new() and never
+ * called directly.
+ *
+ * @param log          The #PurpleLog to delete
+ * @param io_priority  The io priority of the request
+ * @param cancellable  (allow-none): #GCancellable object
+ * @param cb           (allow-none): A #GAsyncReadyCallback to call when the request is satisfied
+ * @param userdata     (allow-none): The data to pass to callback function
+ *
+ * @since 2.8.0
+ */
+void purple_log_common_deleter_async(PurpleLog *log, gint io_priority, GCancellable *cancellable,
+	GAsyncReadyCallback cb, gpointer userdata);
+
+/**
+ * Finish asynchronously deleting a log
+ *
+ * @param log          The #PurpleLog to delete
+ * @param res          A #GAsyncResult
+ * @param error        (out) (allow-none): a #GError location to store the error
+ *
+ * @return             %TRUE if the log was deleted, %FALSE otherwise
+ *
+ * @since 2.8.0
+ */
+gboolean purple_log_common_deleter_finish(PurpleLog *log, GAsyncResult *res, GError **error);
+
+/**
  * Checks to see if a log is deletable
  *
  * This function should only be used with logs that are written
@@ -470,9 +972,9 @@ gboolean purple_log_common_deleter(Purpl
  * It should only be passed to purple_log_logger_new() and never
  * called directly.
  *
- * @param log      The PurpleLog to check.
+ * @param log         The #PurpleLog to check
  *
- * @return A boolean indicating if the log is deletable.
+ * @return            %TRUE if the log is deletable, %FALSE otherwise
  */
 gboolean purple_log_common_is_deletable(PurpleLog *log);
 
@@ -481,103 +983,109 @@ gboolean purple_log_common_is_deletable(
 /******************************************/
 /** @name Logger Functions                */
 /******************************************/
+
 /*@{*/
 
 /**
  * Creates a new logger
  *
- * @param id           The logger's id.
- * @param name         The logger's name.
- * @param functions    The number of functions being passed. The following
- *                     functions are currently available (in order): @c create,
- *                     @c write, @c finalize, @c list, @c read, @c size,
- *                     @c total_size, @c list_syslog, @c get_log_sets,
- *                     @c remove, @c is_deletable.
- *                     For details on these functions, see PurpleLogLogger.
- *                     Functions may not be skipped. For example, passing
- *                     @c create and @c write is acceptable (for a total of
- *                     two functions). Passing @c create and @c finalize,
- *                     however, is not. To accomplish that, the caller must
- *                     pass @c create, @c NULL (a placeholder for @c write),
- *                     and @c finalize (for a total of 3 functions).
+ * @param id          The logger's id
+ * @param name        The logger's name
+ * @param functions   The number of functions being passed. The following
+ *                    functions are currently available (in order): @c create,
+ *                    @c write, @c finalize, @c list, @c read, @c size,
+ *                    @c total_size, @c list_syslog, @c get_log_sets,
+ *                    @c remove, @c is_deletable.
+ *                    For details on these functions, see PurpleLogLogger.
+ *                    Functions may not be skipped. For example, passing
+ *                    @c create and @c write is acceptable (for a total of
+ *                    two functions). Passing @c create and @c finalize,
+ *                    however, is not. To accomplish that, the caller must
+ *                    pass @c create, @c %NULL (a placeholder for @c write),
+ *                    and @c finalize (for a total of 3 functions).
  *
- * @return The new logger
+ * @return            The new logger
  */
-PurpleLogLogger *purple_log_logger_new(const char *id, const char *name, int functions, ...);
+PurpleLogLogger *purple_log_logger_new(const gchar *id, const gchar *name, gint functions, ...);
 
 /**
  * Frees a logger
  *
- * @param logger       The logger to free
+ * @param logger      The logger to free
  */
 void purple_log_logger_free(PurpleLogLogger *logger);
 
 /**
  * Adds a new logger
  *
- * @param logger       The new logger to add
+ * @param logger      The new logger to add
  */
-void purple_log_logger_add (PurpleLogLogger *logger);
+void purple_log_logger_add(PurpleLogLogger *logger);
 
 /**
- *
  * Removes a logger
  *
- * @param logger       The logger to remove
+ * @param logger      The logger to remove
  */
-void purple_log_logger_remove (PurpleLogLogger *logger);
+void purple_log_logger_remove(PurpleLogLogger *logger);
 
 /**
- *
  * Sets the current logger
  *
- * @param logger       The logger to set
+ * @param logger      The logger to set
  */
-void purple_log_logger_set (PurpleLogLogger *logger);
+void purple_log_logger_set(PurpleLogLogger *logger);
 
 /**
- *
  * Returns the current logger
  *
- * @return logger      The current logger
+ * @return logger     The current logger
  */
-PurpleLogLogger *purple_log_logger_get (void);
+PurpleLogLogger *purple_log_logger_get(void);
 
 /**
- * Returns a GList containing the IDs and names of the registered
- * loggers.
+ * Returns a list of all the available loggers
  *
- * @return The list of IDs and names.
+ * @return loggers    All available loggers
+ *
+ * @since 2.8.0
  */
+GSList *purple_log_logger_get_all(void);
+
+/**
+ * Gets a #GList containing the IDs and names of the registered loggers.
+ *
+ * @return            The list of IDs and names
+ */
 GList *purple_log_logger_get_options(void);
 
+/*@}*/
+
 /**************************************************************************/
 /** @name Log Subsystem                                                   */
 /**************************************************************************/
+
 /*@{*/
 
 /**
- * Initializes the log subsystem.
+ * Initializes the log subsystem
  */
 void purple_log_init(void);
 
 /**
- * Returns the log subsystem handle.
+ * Returns the log subsystem handle
  *
- * @return The log subsystem handle.
+ * @return            The log subsystem handle
  */
 void *purple_log_get_handle(void);
 
 /**
- * Uninitializes the log subsystem.
+ * Uninitializes the log subsystem
  */
 void purple_log_uninit(void);
 
 /*@}*/
 
+G_END_DECLS
 
-#ifdef __cplusplus
-}
-#endif
-
 #endif /* _PURPLE_LOG_H_ */
============================================================
--- libpurple/status.c	1fe615918cf2ee8aebf62008f21b0b5b7aa834ae
+++ libpurple/status.c	fd865c7f897eb6d5df5653191a7941b3a20c1d85
@@ -35,6 +35,8 @@
 #include "prefs.h"
 #include "status.h"
 
+static GCancellable *write_status_cancel = NULL;
+
 /**
  * A type of status.
  */
@@ -645,8 +647,9 @@ notify_buddy_status_update(PurpleBuddy *
 		log = purple_account_get_log(purple_buddy_get_account(buddy), FALSE);
 		if (log != NULL)
 		{
-			purple_log_write(log, PURPLE_MESSAGE_SYSTEM, buddy_alias,
-			               current_time, logtmp);
+			purple_log_write_async(log, PURPLE_MESSAGE_SYSTEM, buddy_alias,
+				current_time, logtmp, G_PRIORITY_DEFAULT, write_status_cancel,
+				NULL, NULL);
 		}
 
 		g_free(tmp);
@@ -1267,8 +1270,9 @@ update_buddy_idle(PurpleBuddy *buddy, Pu
 				tmp2 = g_markup_escape_text(tmp, -1);
 				g_free(tmp);
 
-				purple_log_write(log, PURPLE_MESSAGE_SYSTEM,
-				purple_buddy_get_alias(buddy), current_time, tmp2);
+				purple_log_write_async(log, PURPLE_MESSAGE_SYSTEM,
+					purple_buddy_get_alias(buddy), current_time, tmp2,
+					G_PRIORITY_DEFAULT, write_status_cancel, NULL, NULL);
 				g_free(tmp2);
 			}
 		}
@@ -1287,8 +1291,9 @@ update_buddy_idle(PurpleBuddy *buddy, Pu
 				tmp2 = g_markup_escape_text(tmp, -1);
 				g_free(tmp);
 
-				purple_log_write(log, PURPLE_MESSAGE_SYSTEM,
-				purple_buddy_get_alias(buddy), current_time, tmp2);
+				purple_log_write_async(log, PURPLE_MESSAGE_SYSTEM,
+					purple_buddy_get_alias(buddy), current_time, tmp2,
+					G_PRIORITY_DEFAULT, write_status_cancel, NULL, NULL);
 				g_free(tmp2);
 			}
 		}
@@ -1354,9 +1359,11 @@ purple_presence_set_idle(PurplePresence 
 
 				msg = g_markup_escape_text(tmp, -1);
 				g_free(tmp);
-				purple_log_write(log, PURPLE_MESSAGE_SYSTEM,
-				                 purple_account_get_username(account),
-				                 (idle ? idle_time : current_time), msg);
+
+				purple_log_write_async(log, PURPLE_MESSAGE_SYSTEM,
+					purple_account_get_username(account),
+					idle ? idle_time : current_time, msg,
+					G_PRIORITY_DEFAULT, write_status_cancel, NULL, NULL);
 				g_free(msg);
 			}
 		}
@@ -1664,6 +1671,8 @@ purple_status_init(void)
 {
 	void *handle = purple_status_get_handle();
 
+	write_status_cancel = g_cancellable_new();
+
 	purple_prefs_add_none("/purple/status");
 	purple_prefs_add_none("/purple/status/scores");
 
@@ -1716,5 +1725,11 @@ purple_status_uninit(void)
 void
 purple_status_uninit(void)
 {
+	if (write_status_cancel != NULL) {
+		g_cancellable_cancel(write_status_cancel);
+		g_object_unref(write_status_cancel);
+		write_status_cancel = NULL;
+	}
+
 	purple_prefs_disconnect_by_handle(purple_prefs_get_handle());
 }
============================================================
--- pidgin/gtklog.c	f9d82fe3e8fab18648c330dbfffb92f146b5062f
+++ pidgin/gtklog.c	1a7b27749c5c448a40a40c92cbf832f0501b9171
@@ -39,21 +39,200 @@
 #include "gtklog.h"
 #include "gtkutils.h"
 
-static GHashTable *log_viewers = NULL;
-static void populate_log_tree(PidginLogViewer *lv);
-static PidginLogViewer *syslog_viewer = NULL;
 
-struct log_viewer_hash_t {
-	PurpleLogType type;
-	char *buddyname;
+// PidginLogViewer members are stored here to avoid an ABI break, will be moved back in 3.x
+typedef struct {
+	PidginLogViewer    *lv;            /**< The rest of the struct */
+	gsize              size;           /**< The actual value of the total log size */
+	GtkWidget          *list_bar;      /**< The progress bar */
+	GtkWidget          *search_bar;    /**< The progess bar for searches */
+	GCancellable       *read_cancel;   /**< A GCancellable to stop any reads */
+	GCancellable       *search_cancel; /**< A GCancellable to stop any searches */
+	GCancellable       *list_cancel;   /**< A GCancellable to stop any folder listings */
+	gboolean           selected;       /**< A gboolean to indicate the user has already selected a log */
+	PurpleLogType      type;           /**< The log type of the window */
+#if GTK_CHECK_VERSION(2, 20, 0)
+	GtkWidget          *spinner;       /**< A spinner to indicate a read is in progress */
+#endif
+} PidginLogViewerEx;
+
+typedef struct {
+	PidginLogViewerEx *log_viewer;
+	PurpleLog *log;
+
+	gboolean is_window_open;
+	gulong destroy_handler_id;
+
+	guint count;
+	guint total;
+} _pidgin_log_data;
+
+typedef struct {
+	_pidgin_log_data *log_data;
+	PurpleLog *log;
+} _pidgin_search_callback_data;
+
+typedef struct {
+	_pidgin_log_data *log_data;
+	GtkMenuPositionFunc func;
+	GtkTreeIter *iter;
+} log_menu_callback_data;
+
+typedef struct {
+	_pidgin_log_data *log_data;
+	GtkTreeIter *iter;
+	guint destroy_handler_id;
+} log_delete_callback_data;
+
+typedef struct {
 	PurpleAccount *account;
 	PurpleContact *contact;
-};
+	PurpleLogType type;
+	gchar *buddyname;
+} log_viewer_hash_t;
 
-static guint log_viewer_hash(gconstpointer data)
+
+static void pidgin_log_data_free(_pidgin_log_data *);
+static void pidgin_window_destroy_cb(_pidgin_log_data *);
+static guint log_viewer_hash(gconstpointer);
+static gboolean log_viewer_equal(gconstpointer, gconstpointer);
+static void select_first_log(PidginLogViewerEx *);
+static const gchar *log_get_date(PurpleLog *);
+static void pidgin_log_search_done_cb(_pidgin_log_data *);
+static void pidgin_log_search_cb(GObject *, GAsyncResult *, gpointer);
+static void search_cb(GtkWidget *, PidginLogViewerEx *);
+static void destroy_cb(GtkWidget *, gint, log_viewer_hash_t *);
+static void log_row_activated_cb(GtkTreeView *, GtkTreePath *, GtkTreeViewColumn *);
+static void delete_log_cleanup_cb(log_delete_callback_data *);
+static void pidgin_log_delete_log_cb(GObject *, GAsyncResult *, gpointer);
+static void delete_log_cb(log_delete_callback_data *);
+static void remove_delete_log_window(void *);
+static void log_delete_log_cb(GtkWidget *, log_menu_callback_data *);
+static void log_menu_callback_data_free(log_menu_callback_data *);
+static void log_show_popup_menu(GtkWidget *, GdkEventButton *, log_menu_callback_data *);
+static gboolean log_button_press_cb(GtkWidget *, GdkEventButton *, PidginLogViewerEx *);
+static gboolean log_popup_menu_cb(GtkWidget *, PidginLogViewerEx *);
+static gboolean search_find_cb(gpointer);
+static void pidgin_log_read_cb(GObject *, GAsyncResult *, gpointer);
+static void log_select_cb(GtkTreeSelection *, PidginLogViewerEx *);
+static void populate_log_tree(PidginLogViewerEx *);
+static PidginLogViewerEx *display_log_viewer_nonblocking(log_viewer_hash_t *,
+	const gchar *, GtkWidget *, gboolean);
+static void insert_log_viewer_log(PidginLogViewerEx *, PurpleLog *);
+static void append_log_viewer_logs(PidginLogViewerEx *, GList *);
+static void pidgin_log_done_cb(_pidgin_log_data *);
+static void pidgin_log_viewer_update_list_bar(_pidgin_log_data *);
+static void pidgin_log_viewer_update_search_bar(_pidgin_log_data *);
+static void pidgin_log_size_cb(GObject *, GAsyncResult *, gpointer);
+static void pidgin_log_list_cb(GObject *, GAsyncResult *, gpointer);
+static void pidgin_log_system_list_cb(GObject *, GAsyncResult *, gpointer);
+
+
+static GHashTable *log_viewers = NULL;
+static PidginLogViewerEx *syslog_viewer = NULL;
+
+// To be used until PurpleLog changes to using a GDateTime
+GType purple_struct_tm_get_type(void);
+static gpointer purple_struct_tm_copy(gpointer);
+
+#define PURPLE_STRUCT_TM_GET_TYPE (purple_struct_tm_get_type())
+
+#if GLIB_CHECK_VERSION(2, 26, 0)
+
+typedef struct tm PurpleStructTM;
+G_DEFINE_BOXED_TYPE (PurpleStructTM, purple_struct_tm, purple_struct_tm_copy, g_free);
+
+#else
+
+GType
+purple_struct_tm_get_type(void)
 {
-	const struct log_viewer_hash_t *viewer = data;
+	static GType object_type = 0;
 
+	if (G_UNLIKELY (!object_type))
+		object_type = g_boxed_type_register_static(g_intern_static_string("PurpleStructTm"),
+			purple_struct_tm_copy, g_free);
+
+	return object_type;
+}
+
+#endif
+
+static gpointer
+purple_struct_tm_copy(gpointer original)
+{
+	struct tm *copy;
+
+	copy = g_new(struct tm, 1);
+	memcpy(copy, original, sizeof(struct tm));
+
+	return copy;
+}
+
+static void
+pidgin_log_data_free(_pidgin_log_data *data)
+{
+	if (data->destroy_handler_id > 0)
+		g_signal_handler_disconnect(data->log_viewer->lv->window,
+			data->destroy_handler_id);
+
+	g_free(data);
+}
+
+static void
+pidgin_window_destroy_cb(_pidgin_log_data *data)
+{
+	data->destroy_handler_id = 0;
+	data->is_window_open = FALSE;
+}
+
+static void
+pidgin_log_viewer_ex_set_read_cancel(PidginLogViewerEx *lv_ex, GCancellable *cancel)
+{
+	if (lv_ex->read_cancel != NULL) {
+		g_cancellable_cancel(lv_ex->read_cancel);
+		g_object_unref(lv_ex->read_cancel);
+	}
+
+	if (cancel != NULL)
+		g_object_ref(cancel);
+
+	lv_ex->read_cancel = cancel;
+}
+
+static void
+pidgin_log_viewer_ex_set_search_cancel(PidginLogViewerEx *lv_ex, GCancellable *cancel)
+{
+	if (lv_ex->search_cancel != NULL) {
+		g_cancellable_cancel(lv_ex->search_cancel);
+		g_object_unref(lv_ex->search_cancel);
+	}
+
+	if (cancel != NULL)
+		g_object_ref(cancel);
+
+	lv_ex->search_cancel = cancel;
+}
+
+static void
+pidgin_log_viewer_ex_set_list_cancel(PidginLogViewerEx *lv_ex, GCancellable *cancel)
+{
+	if (lv_ex->list_cancel != NULL) {
+		g_cancellable_cancel(lv_ex->list_cancel);
+		g_object_unref(lv_ex->list_cancel);
+	}
+
+	if (cancel != NULL)
+		g_object_ref(cancel);
+
+	lv_ex->list_cancel = cancel;
+}
+
+static guint
+log_viewer_hash(gconstpointer data)
+{
+	const log_viewer_hash_t *viewer = data;
+
 	if (viewer->contact != NULL)
 		return g_direct_hash(viewer->contact);
 
@@ -61,24 +240,20 @@ static guint log_viewer_hash(gconstpoint
 		g_str_hash(purple_account_get_username(viewer->account));
 }
 
-static gboolean log_viewer_equal(gconstpointer y, gconstpointer z)
+static gboolean
+log_viewer_equal(gconstpointer y, gconstpointer z)
 {
-	const struct log_viewer_hash_t *a, *b;
-	int ret;
-	char *normal;
+	const log_viewer_hash_t *a = y, *b = z;
+	gint ret;
+	gchar *normal;
 
-	a = y;
-	b = z;
-
 	if (a->contact != NULL) {
 		if (b->contact != NULL)
 			return (a->contact == b->contact);
 		else
 			return FALSE;
-	} else {
-		if (b->contact != NULL)
-			return FALSE;
-	}
+	} else if (b->contact != NULL)
+		return FALSE;
 
 	normal = g_strdup(purple_normalize(a->account, a->buddyname));
 	ret = (a->account == b->account) &&
@@ -88,30 +263,30 @@ static gboolean log_viewer_equal(gconstp
 	return ret;
 }
 
-static void select_first_log(PidginLogViewer *lv)
+static void
+select_first_log(PidginLogViewerEx *lv_ex)
 {
-	GtkTreeModel *model;
+	PidginLogViewer *lv = lv_ex->lv;
+	GtkTreeModel *model = GTK_TREE_MODEL(lv->treestore);
 	GtkTreeIter iter, it;
 	GtkTreePath *path;
 
-	model = GTK_TREE_MODEL(lv->treestore);
-
 	if (!gtk_tree_model_get_iter_first(model, &iter))
 		return;
 
 	path = gtk_tree_model_get_path(model, &iter);
-	if (gtk_tree_model_iter_children(model, &it, &iter))
-	{
+
+	if (gtk_tree_model_iter_children(model, &it, &iter)) {
 		gtk_tree_view_expand_row(GTK_TREE_VIEW(lv->treeview), path, TRUE);
 		path = gtk_tree_model_get_path(model, &it);
 	}
 
 	gtk_tree_selection_select_path(gtk_tree_view_get_selection(GTK_TREE_VIEW(lv->treeview)), path);
-
 	gtk_tree_path_free(path);
 }
 
-static const char *log_get_date(PurpleLog *log)
+static const gchar *
+log_get_date(PurpleLog *log)
 {
 	if (log->tm)
 		return purple_date_format_full(log->tm);
@@ -119,81 +294,158 @@ static const char *log_get_date(PurpleLo
 		return purple_date_format_full(localtime(&log->time));
 }
 
-static void search_cb(GtkWidget *button, PidginLogViewer *lv)
+static void
+pidgin_log_search_done_cb(_pidgin_log_data *pidgin_log_data)
 {
-	const char *search_term = gtk_entry_get_text(GTK_ENTRY(lv->entry));
+	if (pidgin_log_data->is_window_open) {
+		PidginLogViewerEx *lv_ex = pidgin_log_data->log_viewer;
+
+		if (!lv_ex->selected)
+			select_first_log(lv_ex);
+
+		gtk_widget_hide(lv_ex->search_bar);
+	}
+
+	pidgin_log_data_free(pidgin_log_data);
+}
+
+static void
+pidgin_log_search_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_pidgin_search_callback_data *callback_data = userdata;
+	_pidgin_log_data *pidgin_log_data = callback_data->log_data;
+	PurpleLog *log = callback_data->log;
+	GError *err = NULL;
+	gchar *text;
+
+	text = purple_log_read_finish(log, res, &err);
+	pidgin_log_data->count--;
+
+	if (text == NULL) {
+		if (err->code != G_IO_ERROR_CANCELLED)
+			purple_debug_error("gtklog", "Error reading file during search: %s\n", err->message);
+	} else if (pidgin_log_data->is_window_open) {
+		PidginLogViewer *lv = pidgin_log_data->log_viewer->lv;
+		GtkTreeIter iter;
+
+		if (*text && purple_strcasestr(text, lv->search)) {
+			gtk_tree_store_append(lv->treestore, &iter, NULL);
+			gtk_tree_store_set(lv->treestore, &iter, 0, log_get_date(log), 1, log, -1);
+		}
+	}
+
+	g_clear_error(&err);
+	pidgin_log_viewer_update_search_bar(pidgin_log_data);
+
+	if (pidgin_log_data->count < 1)
+		pidgin_log_search_done_cb(pidgin_log_data);
+
+	g_free(callback_data);
+}
+
+static void
+search_cb(GtkWidget *button, PidginLogViewerEx *lv_ex)
+{
+	_pidgin_log_data *pidgin_log_data;
+	PidginLogViewer *lv = lv_ex->lv;
+	GtkIMHtml *imhtml;
+	GCancellable *cancel;
 	GList *logs;
+	const gchar *search_term;
+	guint length;
 
+	search_term = gtk_entry_get_text(GTK_ENTRY(lv->entry));
+	imhtml = GTK_IMHTML(lv->imhtml);
+
 	if (!(*search_term)) {
 		/* reset the tree */
+		pidgin_log_viewer_ex_set_search_cancel(lv_ex, NULL);
+
 		gtk_tree_store_clear(lv->treestore);
-		populate_log_tree(lv);
+		populate_log_tree(lv_ex);
+
 		g_free(lv->search);
 		lv->search = NULL;
-		gtk_imhtml_search_clear(GTK_IMHTML(lv->imhtml));
-		select_first_log(lv);
+
+		gtk_imhtml_search_clear(imhtml);
+		select_first_log(lv_ex);
+
 		return;
 	}
 
-	if (lv->search != NULL && !strcmp(lv->search, search_term))
-	{
+	if (lv->search != NULL && !strcmp(lv->search, search_term)) {
 		/* Searching for the same term acts as "Find Next" */
-		gtk_imhtml_search_find(GTK_IMHTML(lv->imhtml), lv->search);
+		gtk_imhtml_search_find(imhtml, lv->search);
+
 		return;
 	}
 
-	pidgin_set_cursor(lv->window, GDK_WATCH);
+	length = g_list_length(lv->logs);
 
+	if (length < 1)
+		return;
+
 	g_free(lv->search);
 	lv->search = g_strdup(search_term);
 
 	gtk_tree_store_clear(lv->treestore);
-	gtk_imhtml_clear(GTK_IMHTML(lv->imhtml));
+	gtk_imhtml_clear(imhtml);
+	lv_ex->selected = FALSE;
 
-	for (logs = lv->logs; logs != NULL; logs = logs->next) {
-		char *read = purple_log_read((PurpleLog*)logs->data, NULL);
-		if (read && *read && purple_strcasestr(read, search_term)) {
-			GtkTreeIter iter;
-			PurpleLog *log = logs->data;
+	pidgin_log_data = g_new0(_pidgin_log_data, 1);
+	pidgin_log_data->is_window_open = TRUE;
+	pidgin_log_data->log_viewer = lv_ex;
+	pidgin_log_data->count = pidgin_log_data->total = length;
 
-			gtk_tree_store_append (lv->treestore, &iter, NULL);
-			gtk_tree_store_set(lv->treestore, &iter,
-					   0, log_get_date(log),
-					   1, log, -1);
-		}
-		g_free(read);
+	pidgin_log_data->destroy_handler_id = g_signal_connect_swapped(lv->window,
+		"destroy", G_CALLBACK(pidgin_window_destroy_cb), pidgin_log_data);
+
+	gtk_widget_show(lv_ex->search_bar);
+	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(lv_ex->search_bar), 0.0);
+
+	cancel = g_cancellable_new();
+	pidgin_log_viewer_ex_set_search_cancel(lv_ex, cancel);
+
+	for (logs = lv->logs; logs != NULL; logs = g_list_next(logs)) {
+		_pidgin_search_callback_data *callback_data;
+		PurpleLog *log = logs->data;
+
+		callback_data = g_new0(_pidgin_search_callback_data, 1);
+		callback_data->log_data = pidgin_log_data;
+		callback_data->log = log;
+
+		purple_log_read_async(log, NULL, G_PRIORITY_DEFAULT_IDLE, cancel,
+			pidgin_log_search_cb, callback_data);
 	}
 
-	select_first_log(lv);
-	pidgin_clear_cursor(lv->window);
+	g_object_unref(cancel);
 }
 
-static void destroy_cb(GtkWidget *w, gint resp, struct log_viewer_hash_t *ht) {
-	PidginLogViewer *lv = syslog_viewer;
+static void
+destroy_cb(GtkWidget *w, gint resp, log_viewer_hash_t *ht)
+{
+	PidginLogViewerEx *lv_ex = syslog_viewer;
+	PidginLogViewer *lv;
+	GList *logs;
 
-#ifdef _WIN32
+#ifdef G_OS_WIN32
 	if (resp == GTK_RESPONSE_HELP) {
 		GtkTreeSelection *sel;
 		GtkTreeIter iter;
 		GtkTreeModel *model;
 		PurpleLog *log = NULL;
-		char *logdir;
+		gchar *logdir;
 
 		if (ht != NULL)
-			lv = g_hash_table_lookup(log_viewers, ht);
-		model = GTK_TREE_MODEL(lv->treestore);
+			lv_ex = g_hash_table_lookup(log_viewers, ht);
 
+		lv = lv_ex->lv;
+		model = GTK_TREE_MODEL(lv->treestore);
 		sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(lv->treeview));
-		if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
-			GValue val;
 
-			val.g_type = 0;
-			gtk_tree_model_get_value (model, &iter, 1, &val);
-			log = g_value_get_pointer(&val);
-			g_value_unset(&val);
-		}
+		if (gtk_tree_selection_get_selected(sel, &model, &iter))
+			gtk_tree_model_get(model, &iter, 1, &log, -1);
 
-
 		if (log == NULL)
 			logdir = g_build_filename(purple_user_dir(), "logs", NULL);
 		else
@@ -201,12 +453,13 @@ static void destroy_cb(GtkWidget *w, gin
 
 		winpidgin_shell_execute(logdir, "explore", NULL);
 		g_free(logdir);
+
 		return;
 	}
 #endif
 
 	if (ht != NULL) {
-		lv = g_hash_table_lookup(log_viewers, ht);
+		lv_ex = g_hash_table_lookup(log_viewers, ht);
 		g_hash_table_remove(log_viewers, ht);
 
 		g_free(ht->buddyname);
@@ -214,50 +467,81 @@ static void destroy_cb(GtkWidget *w, gin
 	} else
 		syslog_viewer = NULL;
 
+	lv = lv_ex->lv;
+
 	purple_request_close_with_handle(lv);
 
-	g_list_foreach(lv->logs, (GFunc)purple_log_free, NULL);
+	for (logs = lv->logs; logs != NULL; logs = g_list_next(logs)) 
+		purple_log_free(logs->data);
+
 	g_list_free(lv->logs);
-
 	g_free(lv->search);
 	g_free(lv);
 
+	if (lv_ex->read_cancel != NULL) {
+		g_cancellable_cancel(lv_ex->read_cancel);
+		g_object_unref(lv_ex->read_cancel);
+	}
+
+	if (lv_ex->search_cancel != NULL) {
+		g_cancellable_cancel(lv_ex->search_cancel);
+		g_object_unref(lv_ex->search_cancel);
+	}
+
+	if (lv_ex->list_cancel != NULL) {
+		g_cancellable_cancel(lv_ex->list_cancel);
+		g_object_unref(lv_ex->list_cancel);
+	}
+
+	g_free(lv_ex);
 	gtk_widget_destroy(w);
 }
 
-static void log_row_activated_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *col, PidginLogViewer *viewer) {
+static void
+log_row_activated_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *col)
+{
 	if (gtk_tree_view_row_expanded(tv, path))
 		gtk_tree_view_collapse_row(tv, path);
 	else
 		gtk_tree_view_expand_row(tv, path, FALSE);
 }
 
-static void delete_log_cleanup_cb(gpointer *data)
+static void
+delete_log_cleanup_cb(log_delete_callback_data *data)
 {
-	g_free(data[1]); /* iter */
+	if (data->destroy_handler_id > 0)
+		g_signal_handler_disconnect(data->log_data->log_viewer->lv->window,
+			data->destroy_handler_id);
+
+	pidgin_log_data_free(data->log_data);
+	g_free(data->iter);
 	g_free(data);
 }
 
-static void delete_log_cb(gpointer *data)
+static void
+pidgin_log_delete_log_cb(GObject *object, GAsyncResult *res, gpointer userdata)
 {
-	if (!purple_log_delete((PurpleLog *)data[2]))
-	{
-		purple_notify_error(NULL, NULL, _("Log Deletion Failed"),
-		                  _("Check permissions and try again."));
-	}
-	else
-	{
-		GtkTreeStore *treestore = data[0];
-		GtkTreeIter *iter = (GtkTreeIter *)data[1];
+	log_delete_callback_data *temp = userdata;
+	GError *err = NULL;
+	gboolean result;
+
+	/* Technically speaking, purple_log_delete_finish doesn't care about
+	   the first argument */
+	result = purple_log_delete_finish(NULL, res, &err);
+
+	if (!result)
+		purple_notify_error(NULL, NULL, "Log Deletion Failed",
+			err->message);
+	else {
+		GtkTreeStore *treestore = temp->log_data->log_viewer->lv->treestore;
+		GtkTreeIter *iter = temp->iter;
 		GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(treestore), iter);
 		gboolean first = !gtk_tree_path_prev(path);
 
-		if (!gtk_tree_store_remove(treestore, iter) && first)
-		{
+		if (!gtk_tree_store_remove(treestore, iter) && first) {
 			/* iter was the last child at its level */
 
-			if (gtk_tree_path_up(path))
-			{
+			if (gtk_tree_path_up(path)) {
 				gtk_tree_model_get_iter(GTK_TREE_MODEL(treestore), iter, path);
 				gtk_tree_store_remove(treestore, iter);
 			}
@@ -266,46 +550,60 @@ static void delete_log_cb(gpointer *data
 		gtk_tree_path_free(path);
 	}
 
-	delete_log_cleanup_cb(data);
+	g_clear_error(&err);
+	delete_log_cleanup_cb(temp);
 }
 
-static void log_delete_log_cb(GtkWidget *menuitem, gpointer *data)
+static void
+delete_log_cb(log_delete_callback_data *data)
 {
-	PidginLogViewer *lv = data[0];
-	PurpleLog *log = data[1];
-	const char *time = log_get_date(log);
-	const char *name;
-	char *tmp;
-	gpointer *data2;
+	purple_log_delete_async(data->log_data->log, G_PRIORITY_DEFAULT, NULL,
+		pidgin_log_delete_log_cb, data);
+}
 
-	if (log->type == PURPLE_LOG_IM)
-	{
-		PurpleBuddy *buddy = purple_find_buddy(log->account, log->name);
+static void
+remove_delete_log_window(void *handle)
+{
+	purple_request_close(PURPLE_REQUEST_ACTION, handle);
+}
+
+static void
+log_delete_log_cb(GtkWidget *menuitem, log_menu_callback_data *data)
+{
+	log_delete_callback_data *data2;
+	PidginLogViewerEx *lv_ex = data->log_data->log_viewer;
+	PurpleBuddy *buddy;
+	PurpleChat *chat;
+	PurpleLog *log = data->log_data->log;
+	const gchar *name, *time = log_get_date(log);
+	gchar *str;
+	void *handle;
+
+	if (log->type == PURPLE_LOG_IM) {
+		buddy = purple_find_buddy(log->account, log->name);
+
 		if (buddy != NULL)
 			name = purple_buddy_get_contact_alias(buddy);
 		else
 			name = log->name;
 
-		tmp = g_strdup_printf(_("Are you sure you want to permanently delete the log of the "
-		                        "conversation with %s which started at %s?"), name, time);
-	}
-	else if (log->type == PURPLE_LOG_CHAT)
-	{
-		PurpleChat *chat = purple_blist_find_chat(log->account, log->name);
+		str = g_strdup_printf(_("Are you sure you want to permanently delete the log of the "
+			"conversation with %s which started at %s?"), name, time);
+	} else if (log->type == PURPLE_LOG_CHAT) {
+		chat = purple_blist_find_chat(log->account, log->name);
+
 		if (chat != NULL)
 			name = purple_chat_get_name(chat);
 		else
 			name = log->name;
 
-		tmp = g_strdup_printf(_("Are you sure you want to permanently delete the log of the "
-		                        "conversation in %s which started at %s?"), name, time);
-	}
-	else if (log->type == PURPLE_LOG_SYSTEM)
-	{
-		tmp = g_strdup_printf(_("Are you sure you want to permanently delete the system log "
-		                        "which started at %s?"), time);
-	}
-	else
+		str = g_strdup_printf(_("Are you sure you want to permanently delete the log of the "
+			"conversation in %s which started at %s?"), name, time);
+	} else if (log->type == PURPLE_LOG_SYSTEM) {
+		str = g_strdup_printf(_("Are you sure you want to permanently delete the system log "
+			"which started at %s?"), time);
+	} else
+		/* Stuff will get freed elsewhere */
 		g_return_if_reached();
 
 	/* The only way to free data in all cases is to tie it to the menuitem with
@@ -313,174 +611,268 @@ static void log_delete_log_cb(GtkWidget 
 	 * delete_log_cb() to delete the log from the log viewer after the file is
 	 * deleted, we have to allocate a new data array and make sure it gets freed
 	 * either way. */
-	data2 = g_new(gpointer, 3);
-	data2[0] = lv->treestore;
-	data2[1] = data[3]; /* iter */
-	data2[2] = log;
-	purple_request_action(lv, NULL, _("Delete Log?"), tmp, 0,
-						NULL, NULL, NULL,
-						data2, 2,
-						_("Delete"), delete_log_cb,
-						_("Cancel"), delete_log_cleanup_cb);
-	g_free(tmp);
+	data2 = g_new(log_delete_callback_data, 1);
+	data2->log_data = data->log_data;
+	data2->iter = g_new(GtkTreeIter, 1);
+	*(data2->iter) = *(data->iter);
+	data2->log_data->count++;
+
+	handle = purple_request_action(lv_ex, NULL, _("Delete Log?"), str, 0,
+		NULL, NULL, NULL, data2, 2,
+		_("Delete"), delete_log_cb,
+		_("Cancel"), delete_log_cleanup_cb);
+
+	g_free(str);
+
+	/* Close the dialog window when the log viewer is closed */
+	data2->destroy_handler_id = g_signal_connect_swapped(lv_ex->lv->window,
+		"destroy", G_CALLBACK(remove_delete_log_window), handle);
 }
 
-static void log_show_popup_menu(GtkWidget *treeview, GdkEventButton *event, gpointer *data)
+static void
+log_menu_callback_data_free(log_menu_callback_data *data)
 {
-	GtkWidget *menu = gtk_menu_new();
-	GtkWidget *menuitem = gtk_menu_item_new_with_label(_("Delete Log..."));
+	_pidgin_log_data *pidgin_log_data = data->log_data;
 
-	if (!purple_log_is_deletable((PurpleLog *)data[1]))
+	pidgin_log_data->count--;
+
+	if (pidgin_log_data->count < 1)
+		pidgin_log_data_free(pidgin_log_data);
+
+	g_free(data->iter);
+	g_free(data);
+}
+
+static void
+log_show_popup_menu(GtkWidget *treeview, GdkEventButton *event, log_menu_callback_data *data)
+{
+	_pidgin_log_data *pidgin_log_data = data->log_data;
+	PidginLogViewer *lv = pidgin_log_data->log_viewer->lv;
+	GtkWidget *menu, *menuitem;
+
+	menu = gtk_menu_new();
+	menuitem = gtk_menu_item_new_with_label(_("Delete Log..."));
+
+	if (!purple_log_is_deletable(data->log_data->log))
 		gtk_widget_set_sensitive(menuitem, FALSE);
 
 	g_signal_connect(menuitem, "activate", G_CALLBACK(log_delete_log_cb), data);
-	g_object_set_data_full(G_OBJECT(menuitem), "log-viewer-data", data, g_free);
+	g_object_set_data_full(G_OBJECT(menuitem), "log-viewer-data", data,
+		(GDestroyNotify) log_menu_callback_data_free);
 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
 	gtk_widget_show_all(menu);
 
-	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, (GtkMenuPositionFunc)data[2], NULL,
-	               (event != NULL) ? event->button : 0,
-	               gdk_event_get_time((GdkEvent *)event));
+	pidgin_log_data->count = 1;
+	pidgin_log_data->is_window_open = TRUE;
+	pidgin_log_data->destroy_handler_id = g_signal_connect_swapped(lv->window,
+		"destroy", G_CALLBACK(pidgin_window_destroy_cb), pidgin_log_data);
+
+	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, data->func, NULL,
+		event != NULL ? event->button : 0,
+		gdk_event_get_time((GdkEvent *) event));
 }
 
-static gboolean log_button_press_cb(GtkWidget *treeview, GdkEventButton *event, PidginLogViewer *lv)
+static gboolean
+log_button_press_cb(GtkWidget *treeview, GdkEventButton *event, PidginLogViewerEx *lv_ex)
 {
-	if (event->type == GDK_BUTTON_PRESS && event->button == 3)
-	{
+	if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
+		log_menu_callback_data *data;
+		GtkTreeModel *model = GTK_TREE_MODEL(lv_ex->lv->treestore);
+		GtkTreeSelection *selection;
 		GtkTreePath *path;
 		GtkTreeIter *iter;
-		GValue val;
 		PurpleLog *log;
-		gpointer *data;
 
-		if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), event->x, event->y, &path, NULL, NULL, NULL))
+		if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), event->x, event->y,
+			&path, NULL, NULL, NULL))
 			return FALSE;
+
 		iter = g_new(GtkTreeIter, 1);
-		gtk_tree_model_get_iter(GTK_TREE_MODEL(lv->treestore), iter, path);
-		val.g_type = 0;
-		gtk_tree_model_get_value(GTK_TREE_MODEL(lv->treestore), iter, 1, &val);
+		gtk_tree_model_get_iter(model, iter, path);
+		selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
+		gtk_tree_selection_select_iter(selection, iter);
+		gtk_tree_model_get(model, iter, 1, &log, -1);
 		gtk_tree_path_free(path);
 
-		log = g_value_get_pointer(&val);
-
-		if (log == NULL)
-		{
+		if (log == NULL) {
 			g_free(iter);
 			return FALSE;
 		}
 
-		data = g_new(gpointer, 4);
-		data[0] = lv;
-		data[1] = log;
-		data[2] = NULL;
-		data[3] = iter;
+		data = g_new(log_menu_callback_data, 1);
+		data->log_data = g_new0(_pidgin_log_data, 1);
+		data->log_data->log_viewer = lv_ex;
+		data->log_data->log = log;
+		data->func = NULL;
+		data->iter = iter;
 
 		log_show_popup_menu(treeview, event, data);
+
 		return TRUE;
 	}
 
 	return FALSE;
 }
 
-static gboolean log_popup_menu_cb(GtkWidget *treeview, PidginLogViewer *lv)
+static gboolean
+log_popup_menu_cb(GtkWidget *treeview, PidginLogViewerEx *lv_ex)
 {
+	log_menu_callback_data *data;
+	PidginLogViewer *lv = lv_ex->lv;
 	GtkTreeSelection *sel;
 	GtkTreeIter *iter;
-	GValue val;
 	PurpleLog *log;
-	gpointer *data;
 
 	iter = g_new(GtkTreeIter, 1);
 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(lv->treeview));
-	if (!gtk_tree_selection_get_selected(sel, NULL, iter))
-	{
+
+	if (!gtk_tree_selection_get_selected(sel, NULL, iter)) {
+		g_free(iter);
 		return FALSE;
 	}
 
-	val.g_type = 0;
-	gtk_tree_model_get_value(GTK_TREE_MODEL(lv->treestore),
-	                         iter, NODE_COLUMN, &val);
+	gtk_tree_model_get(GTK_TREE_MODEL(lv->treestore), iter, NODE_COLUMN, &log, -1);
 
-	log = g_value_get_pointer(&val);
-
-	if (log == NULL)
+	if (log == NULL) {
+		g_free(iter);
 		return FALSE;
+	}
 
-	data = g_new(gpointer, 4);
-	data[0] = lv;
-	data[1] = log;
-	data[2] = pidgin_treeview_popup_menu_position_func;
-	data[3] = iter;
+	data = g_new(log_menu_callback_data, 1);
+	data->log_data = g_new0(_pidgin_log_data, 1);
+	data->log_data->log_viewer = lv_ex;
+	data->log_data->log = log;
+	data->func = pidgin_treeview_popup_menu_position_func;
+	data->iter = iter;
 
 	log_show_popup_menu(treeview, NULL, data);
+
 	return TRUE;
 }
 
-static gboolean search_find_cb(gpointer data)
+static gboolean
+search_find_cb(gpointer userdata)
 {
-	PidginLogViewer *viewer = data;
-	gtk_imhtml_search_find(GTK_IMHTML(viewer->imhtml), viewer->search);
-	g_object_steal_data(G_OBJECT(viewer->entry), "search-find-cb");
+	_pidgin_log_data *pidgin_log_data = userdata;
+
+	if (pidgin_log_data->is_window_open) {
+		PidginLogViewer *lv = pidgin_log_data->log_viewer->lv;
+
+		gtk_imhtml_search_find(GTK_IMHTML(lv->imhtml), lv->search);
+	}
+
+	pidgin_log_data_free(pidgin_log_data);
+
 	return FALSE;
 }
 
-static void log_select_cb(GtkTreeSelection *sel, PidginLogViewer *viewer) {
-	GtkTreeIter iter;
-	GValue val;
-	GtkTreeModel *model = GTK_TREE_MODEL(viewer->treestore);
+static void
+pidgin_log_read_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_pidgin_log_data *pidgin_log_data = userdata;
+
+	if (pidgin_log_data->is_window_open) {
+		PurpleLog *log = pidgin_log_data->log;
+		PidginLogViewerEx *lv_ex = pidgin_log_data->log_viewer;
+		PidginLogViewer *lv = lv_ex->lv;
+		GtkIMHtml *imhtml = GTK_IMHTML(lv->imhtml);
+		GError *err = NULL;
+		gchar *text;
+
+		text = purple_log_read_finish(log, res, &err);
+
+		if (text == NULL) {
+			if (err->code == G_IO_ERROR_CANCELLED) {
+				pidgin_log_data_free(pidgin_log_data);
+				g_clear_error(&err);
+
+				return;
+			}
+
+			text = err->message;
+		}
+
+#if GTK_CHECK_VERSION(2, 20, 0)
+		gtk_widget_show(lv->imhtml);
+		gtk_widget_hide(lv_ex->spinner);
+#endif
+
+		gtk_imhtml_clear(imhtml);
+		gtk_imhtml_set_protocol_name(imhtml,
+			purple_account_get_protocol_name(log->account));
+
+		purple_signal_emit(pidgin_log_get_handle(), "log-displaying", lv, log);
+
+		//gtk_imhtml_append_text is a time killer in loading logs, annoyingly
+		gtk_imhtml_append_text(imhtml, text,
+			GTK_IMHTML_NO_COMMENTS | GTK_IMHTML_NO_TITLE | GTK_IMHTML_NO_SCROLL |
+			(lv->flags & PURPLE_LOG_READ_NO_NEWLINE ? GTK_IMHTML_NO_NEWLINE : 0));
+
+		/* If we have something in the search bar, scroll to it in the current log */
+		if (lv->search != NULL) {
+			gtk_imhtml_search_clear(imhtml);
+			g_idle_add(search_find_cb, pidgin_log_data);
+		} else
+			pidgin_log_data_free(pidgin_log_data);
+
+		g_clear_error(&err);
+	} else
+		pidgin_log_data_free(pidgin_log_data);
+}
+
+static void
+log_select_cb(GtkTreeSelection *sel, PidginLogViewerEx *lv_ex)
+{
+	_pidgin_log_data *pidgin_log_data;
+	PidginLogViewer *lv = lv_ex->lv;
 	PurpleLog *log = NULL;
-	PurpleLogReadFlags flags;
-	char *read = NULL;
+	GtkTreeModel *model = GTK_TREE_MODEL(lv->treestore);
+	GtkTreeIter iter;
+	GCancellable *cancel;
 
 	if (!gtk_tree_selection_get_selected(sel, &model, &iter))
 		return;
 
-	val.g_type = 0;
-	gtk_tree_model_get_value (model, &iter, 1, &val);
-	log = g_value_get_pointer(&val);
-	g_value_unset(&val);
+	lv_ex->selected = TRUE;
+	gtk_tree_model_get(model, &iter, 1, &log, -1);
 
 	if (log == NULL)
 		return;
 
-	pidgin_set_cursor(viewer->window, GDK_WATCH);
-
 	if (log->type != PURPLE_LOG_SYSTEM) {
-		char *title;
+		gchar *title;
+
 		if (log->type == PURPLE_LOG_CHAT)
 			title = g_strdup_printf(_("<span size='larger' weight='bold'>Conversation in %s on %s</span>"),
-									log->name, log_get_date(log));
+				log->name, log_get_date(log));
 		else
 			title = g_strdup_printf(_("<span size='larger' weight='bold'>Conversation with %s on %s</span>"),
-									log->name, log_get_date(log));
+				log->name, log_get_date(log));
 
-		gtk_label_set_markup(GTK_LABEL(viewer->label), title);
+		gtk_label_set_markup(GTK_LABEL(lv->label), title);
 		g_free(title);
 	}
 
-	read = purple_log_read(log, &flags);
-	viewer->flags = flags;
+	pidgin_log_data = g_new0(_pidgin_log_data, 1);
+	pidgin_log_data->is_window_open = TRUE;
+	pidgin_log_data->count = 1;
+	pidgin_log_data->log_viewer = lv_ex;
+	pidgin_log_data->log = log;
+	pidgin_log_data->destroy_handler_id = g_signal_connect_swapped(lv->window,
+		"destroy", G_CALLBACK(pidgin_window_destroy_cb), pidgin_log_data);
 
-	gtk_imhtml_clear(GTK_IMHTML(viewer->imhtml));
-	gtk_imhtml_set_protocol_name(GTK_IMHTML(viewer->imhtml),
-	                            purple_account_get_protocol_name(log->account));
+	cancel = g_cancellable_new();
+	pidgin_log_viewer_ex_set_read_cancel(lv_ex, cancel);
 
-	purple_signal_emit(pidgin_log_get_handle(), "log-displaying", viewer, log);
+#if GTK_CHECK_VERSION(2, 20, 0)
+	gtk_widget_hide(lv->imhtml);
+	gtk_widget_show(lv_ex->spinner);
+#endif
 
-	gtk_imhtml_append_text(GTK_IMHTML(viewer->imhtml), read,
-			       GTK_IMHTML_NO_COMMENTS | GTK_IMHTML_NO_TITLE | GTK_IMHTML_NO_SCROLL |
-			       ((flags & PURPLE_LOG_READ_NO_NEWLINE) ? GTK_IMHTML_NO_NEWLINE : 0));
-	g_free(read);
+	purple_log_read_async(log, &lv->flags, G_PRIORITY_DEFAULT, cancel,
+		pidgin_log_read_cb, pidgin_log_data);
 
-	if (viewer->search != NULL) {
-		guint source;
-		gtk_imhtml_search_clear(GTK_IMHTML(viewer->imhtml));
-		source = g_idle_add(search_find_cb, viewer);
-		g_object_set_data_full(G_OBJECT(viewer->entry), "search-find-cb",
-		                       GINT_TO_POINTER(source), (GDestroyNotify)g_source_remove);
-	}
-
-	pidgin_clear_cursor(viewer->window);
+	g_object_unref(cancel);
 }
 
 /* I want to make this smarter, but haven't come up with a cool algorithm to do so, yet.
@@ -489,26 +881,29 @@ static void log_select_cb(GtkTreeSelecti
  *
  * For now, I'll just make it a flat list.
  */
-static void populate_log_tree(PidginLogViewer *lv)
+static void
+populate_log_tree(PidginLogViewerEx *lv_ex)
      /* Logs are made from trees in real life.
         This is a tree made from logs */
 {
-	const char *month;
-	char prev_top_month[30] = "";
+	PurpleLog *log;
+	PidginLogViewer *lv = lv_ex->lv;
 	GtkTreeIter toplevel, child;
-	GList *logs = lv->logs;
+	GList *logs;
+	const gchar *month;
+	gchar prev_top_month[30] = "";
 
-	while (logs != NULL) {
-		PurpleLog *log = logs->data;
+	gtk_tree_store_clear(lv->treestore);
 
-		month = purple_utf8_strftime(_("%B %Y"),
-		                           log->tm ? log->tm : localtime(&log->time));
+	for (logs = lv->logs; logs != NULL; logs = g_list_next(logs)) {
+		log = logs->data;
 
-		if (strcmp(month, prev_top_month) != 0)
-		{
+		month = purple_utf8_strftime(_("%B %Y"), log->tm ? log->tm : localtime(&log->time));
+
+		if (strcmp(month, prev_top_month) != 0) {
 			/* top level */
 			gtk_tree_store_append(lv->treestore, &toplevel, NULL);
-			gtk_tree_store_set(lv->treestore, &toplevel, 0, month, 1, NULL, -1);
+			gtk_tree_store_set(lv->treestore, &toplevel, 0, month, 1, NULL, 2, log->tm, -1);
 
 			strncpy(prev_top_month, month, sizeof(prev_top_month));
 		}
@@ -516,87 +911,57 @@ static void populate_log_tree(PidginLogV
 		/* sub */
 		gtk_tree_store_append(lv->treestore, &child, &toplevel);
 		gtk_tree_store_set(lv->treestore, &child,
-						   0, log_get_date(log),
-						   1, log,
-						   -1);
-
-		logs = logs->next;
+			0, log_get_date(log), 1, log, -1);
 	}
 }
 
-static PidginLogViewer *display_log_viewer(struct log_viewer_hash_t *ht, GList *logs,
-						const char *title, GtkWidget *icon, int log_size)
+static PidginLogViewerEx *
+display_log_viewer_nonblocking(log_viewer_hash_t *ht,
+	const gchar *title, GtkWidget *icon, gboolean need_log_size)
 {
+	PidginLogViewerEx *lv_ex;
 	PidginLogViewer *lv;
-	GtkWidget *title_box;
-	char *text;
-	GtkWidget *pane;
-	GtkWidget *sw;
 	GtkCellRenderer *rend;
 	GtkTreeViewColumn *col;
 	GtkTreeSelection *sel;
-	GtkWidget *vbox;
-	GtkWidget *frame;
-	GtkWidget *hbox;
-	GtkWidget *find_button;
-	GtkWidget *size_label;
+	GtkWidget *title_box, *pane, *sw, *frame, *find_button, *content_area, *vbox, *hbox;
+	gchar *text;
 
-	if (logs == NULL)
-	{
-		/* No logs were found. */
-		const char *log_preferences = NULL;
-
-		if (ht == NULL) {
-			if (!purple_prefs_get_bool("/purple/logging/log_system"))
-				log_preferences = _("System events will only be logged if the \"Log all status changes to system log\" preference is enabled.");
-		} else {
-			if (ht->type == PURPLE_LOG_IM) {
-				if (!purple_prefs_get_bool("/purple/logging/log_ims"))
-					log_preferences = _("Instant messages will only be logged if the \"Log all instant messages\" preference is enabled.");
-			} else if (ht->type == PURPLE_LOG_CHAT) {
-				if (!purple_prefs_get_bool("/purple/logging/log_chats"))
-					log_preferences = _("Chats will only be logged if the \"Log all chats\" preference is enabled.");
-			}
-			g_free(ht->buddyname);
-			g_free(ht);
-		}
-
-		if(icon != NULL)
-			gtk_widget_destroy(icon);
-
-		purple_notify_info(NULL, title, _("No logs were found"), log_preferences);
-		return NULL;
-	}
-
+	lv_ex = g_new0(PidginLogViewerEx, 1);
 	lv = g_new0(PidginLogViewer, 1);
-	lv->logs = logs;
+	lv->logs = NULL;
+	lv_ex->lv = lv;
 
-	if (ht != NULL)
-		g_hash_table_insert(log_viewers, ht, lv);
+	if (ht != NULL) {
+		lv_ex->type = ht->type;
+		g_hash_table_insert(log_viewers, ht, lv_ex);
+	} else
+		lv_ex->type = PURPLE_LOG_SYSTEM;
 
 	/* Window ***********/
 	lv->window = gtk_dialog_new_with_buttons(title, NULL, 0,
-					     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
-#ifdef _WIN32
+		GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
+	content_area = gtk_dialog_get_content_area(GTK_DIALOG(lv->window));
+
+#ifdef G_OS_WIN32
 	/* Steal the "HELP" response and use it to trigger browsing to the logs folder */
 	gtk_dialog_add_button(GTK_DIALOG(lv->window), _("_Browse logs folder"), GTK_RESPONSE_HELP);
 #endif
 	gtk_container_set_border_width (GTK_CONTAINER(lv->window), PIDGIN_HIG_BOX_SPACE);
 	gtk_dialog_set_has_separator(GTK_DIALOG(lv->window), FALSE);
-	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(lv->window)->vbox), 0);
-	g_signal_connect(G_OBJECT(lv->window), "response",
-					 G_CALLBACK(destroy_cb), ht);
+	gtk_box_set_spacing(GTK_BOX(content_area), 0);
+	g_signal_connect(lv->window, "response", G_CALLBACK(destroy_cb), ht);
 	gtk_window_set_role(GTK_WINDOW(lv->window), "log_viewer");
 
 	/* Icon *************/
 	if (icon != NULL) {
 		title_box = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 		gtk_container_set_border_width(GTK_CONTAINER(title_box), PIDGIN_HIG_BOX_SPACE);
-		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(lv->window)->vbox), title_box, FALSE, FALSE, 0);
+		gtk_box_pack_start(GTK_BOX(content_area), title_box, FALSE, FALSE, 0);
 
 		gtk_box_pack_start(GTK_BOX(title_box), icon, FALSE, FALSE, 0);
 	} else
-		title_box = GTK_DIALOG(lv->window)->vbox;
+		title_box = content_area;
 
 	/* Label ************/
 	lv->label = gtk_label_new(NULL);
@@ -611,47 +976,43 @@ static PidginLogViewer *display_log_view
 	/* Pane *************/
 	pane = gtk_hpaned_new();
 	gtk_container_set_border_width(GTK_CONTAINER(pane), PIDGIN_HIG_BOX_SPACE);
-	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(lv->window)->vbox), pane, TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(content_area), pane, TRUE, TRUE, 0);
 
 	/* List *************/
 	sw = gtk_scrolled_window_new (NULL, NULL);
 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
 	gtk_paned_add1(GTK_PANED(pane), sw);
-	lv->treestore = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
-	lv->treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (lv->treestore));
-	g_object_unref(G_OBJECT(lv->treestore));
+
+	lv->treestore = gtk_tree_store_new(3, G_TYPE_STRING, G_TYPE_POINTER, PURPLE_STRUCT_TM_GET_TYPE);
+	lv->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(lv->treestore));
+	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(lv->treeview), FALSE);
+	pidgin_set_accessible_label(lv->treeview, lv->label);
+	g_object_unref(lv->treestore);
+
 	rend = gtk_cell_renderer_text_new();
 	col = gtk_tree_view_column_new_with_attributes ("time", rend, "markup", 0, NULL);
 	gtk_tree_view_append_column (GTK_TREE_VIEW(lv->treeview), col);
-	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (lv->treeview), FALSE);
 	gtk_container_add (GTK_CONTAINER (sw), lv->treeview);
 
-	populate_log_tree(lv);
+	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(lv->treeview));
 
-	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (lv->treeview));
-	g_signal_connect (G_OBJECT (sel), "changed",
-			G_CALLBACK (log_select_cb),
-			lv);
-	g_signal_connect (G_OBJECT(lv->treeview), "row-activated",
-			G_CALLBACK(log_row_activated_cb),
-			lv);
-	pidgin_set_accessible_label(lv->treeview, lv->label);
+	g_signal_connect(sel, "changed", G_CALLBACK (log_select_cb), lv_ex);
+	g_signal_connect(lv->treeview, "row-activated", G_CALLBACK(log_row_activated_cb), NULL);
+	g_signal_connect(lv->treeview, "button-press-event", G_CALLBACK(log_button_press_cb), lv_ex);
+	g_signal_connect(lv->treeview, "popup-menu", G_CALLBACK(log_popup_menu_cb), lv_ex);
 
-	g_signal_connect(lv->treeview, "button-press-event", G_CALLBACK(log_button_press_cb), lv);
-	g_signal_connect(lv->treeview, "popup-menu", G_CALLBACK(log_popup_menu_cb), lv);
-
 	/* Log size ************/
-	if(log_size) {
-		char *sz_txt = purple_str_size_to_units(log_size);
-		text = g_strdup_printf("<span weight='bold'>%s</span> %s", _("Total log size:"), sz_txt);
-		size_label = gtk_label_new(NULL);
-		gtk_label_set_markup(GTK_LABEL(size_label), text);
-		/*		gtk_paned_add1(GTK_PANED(pane), size_label); */
-		gtk_misc_set_alignment(GTK_MISC(size_label), 0, 0);
-		gtk_box_pack_end(GTK_BOX(GTK_DIALOG(lv->window)->vbox), size_label, FALSE, FALSE, 0);
-		g_free(sz_txt);
+	if (need_log_size) {
+		lv->size_label = gtk_label_new(NULL);
+		lv_ex->size = 0;
+
+		text = g_strdup_printf("<span weight='bold'>%s</span> %s", _("Total log size:"), _("calculating..."));
+		gtk_label_set_markup(GTK_LABEL(lv->size_label), text);
 		g_free(text);
+
+		gtk_misc_set_alignment(GTK_MISC(lv->size_label), 0, 0);
+		gtk_box_pack_end(GTK_BOX(content_area), lv->size_label, FALSE, FALSE, 0);
 	}
 
 	/* A fancy little box ************/
@@ -665,122 +1026,413 @@ static PidginLogViewer *display_log_view
 	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
 	gtk_widget_show(frame);
 
+#if GTK_CHECK_VERSION(2, 20, 0)
+	lv_ex->spinner = gtk_spinner_new();
+	gtk_spinner_start(GTK_SPINNER(lv_ex->spinner));
+	gtk_box_pack_start(GTK_BOX(vbox), lv_ex->spinner, TRUE, FALSE, 0);
+#endif
+
 	/* Search box **********/
 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
 	lv->entry = gtk_entry_new();
+	find_button = gtk_button_new_from_stock(GTK_STOCK_FIND);
+
 	gtk_box_pack_start(GTK_BOX(hbox), lv->entry, TRUE, TRUE, 0);
-	find_button = gtk_button_new_from_stock(GTK_STOCK_FIND);
 	gtk_box_pack_start(GTK_BOX(hbox), find_button, FALSE, FALSE, 0);
-	g_signal_connect(GTK_ENTRY(lv->entry), "activate", G_CALLBACK(search_cb), lv);
-	g_signal_connect(GTK_BUTTON(find_button), "clicked", G_CALLBACK(search_cb), lv);
 
-	select_first_log(lv);
+	g_signal_connect(lv->entry, "activate", G_CALLBACK(search_cb), lv_ex);
+	g_signal_connect(find_button, "clicked", G_CALLBACK(search_cb), lv_ex);
 
+	/* Progress bars **********/
+	lv_ex->list_bar = gtk_progress_bar_new();
+	lv_ex->search_bar = gtk_progress_bar_new();
+
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(lv_ex->list_bar), _("Waiting for logs ..."));
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(lv_ex->search_bar), _("Searching logs ..."));
+
+	gtk_box_pack_start(GTK_BOX(vbox), lv_ex->list_bar, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), lv_ex->search_bar, FALSE, FALSE, 0);
+
 	gtk_widget_show_all(lv->window);
+	gtk_widget_hide(lv_ex->search_bar);
+#if GTK_CHECK_VERSION(2, 20, 0)
+	gtk_widget_hide(lv_ex->spinner);
+#endif
 
-	return lv;
+	lv_ex->read_cancel = NULL;
+	lv_ex->search_cancel = NULL;
+	lv_ex->list_cancel = NULL;
+
+	lv_ex->selected = FALSE;
+
+	return lv_ex;
 }
 
-void pidgin_log_show(PurpleLogType type, const char *buddyname, PurpleAccount *account) {
-	struct log_viewer_hash_t *ht;
-	PidginLogViewer *lv = NULL;
-	const char *name = buddyname;
-	char *title;
+static void
+insert_log_viewer_log(PidginLogViewerEx *lv_ex, PurpleLog *log)
+{
+	/* It runs about twice as quick to insert the logs, rather than re-populate the entire tree
+
+	   This is also a trade-off though, as it means having to update both this function and
+	   populate_log_tree if the tree format is to ever be changed
+	 */
+	PurpleLog *child_log;
+	GtkTreeModel *model;
+	GtkTreeStore *store;
+	GtkTreeIter insertion, inserted_month, month_iter, child;
+	const gchar *month;
+	struct tm *tm;
+
+	store = lv_ex->lv->treestore;
+	model = GTK_TREE_MODEL(store);
+	month = purple_utf8_strftime(_("%B %Y"), log->tm ? log->tm : localtime(&log->time));
+
+	if (!gtk_tree_model_get_iter_first(model, &month_iter)) {
+		/* Appending the month */
+		gtk_tree_store_append(store, &inserted_month, NULL);
+		gtk_tree_store_set(store, &inserted_month, 0, month, 1, NULL, 2, log->tm, -1);
+
+		gtk_tree_store_append(store, &insertion, &inserted_month);
+		gtk_tree_store_set(store, &insertion, 0, log_get_date(log), 1, log, -1);
+
+		return;
+	}
+
+	do {
+		gtk_tree_model_get(model, &month_iter, 2, &tm, -1);
+
+		if (tm != NULL && tm->tm_mon == log->tm->tm_mon && tm->tm_year == log->tm->tm_year) {
+			if (!gtk_tree_model_iter_children(model, &child, &month_iter)) {
+				/* Appending the log to the currently selected month */
+				gtk_tree_store_append(store, &insertion, &month_iter);
+				gtk_tree_store_set(store, &insertion, 0, log_get_date(log), 1, log, -1);
+
+				return;
+			}
+
+			do {
+				gtk_tree_model_get(model, &child, 1, &child_log, -1);
+
+				if (log->time > child_log->time) {
+					/* Prepending the log to the currently selected month */
+					gtk_tree_store_insert_before(store, &insertion, &month_iter, &child);
+					gtk_tree_store_set(store, &insertion, 0, log_get_date(log), 1, log, -1);
+
+					return;
+				}
+
+			} while (gtk_tree_model_iter_next(model, &child));
+
+			/* Appending the log to the currently selected month */
+			gtk_tree_store_append(store, &insertion, &month_iter);
+			gtk_tree_store_set(store, &insertion, 0, log_get_date(log), 1, log, -1);
+
+			return;
+		} else if (tm->tm_year < log->tm->tm_year ||
+			(tm->tm_year == log->tm->tm_year && tm->tm_mon < log->tm->tm_mon))
+		{
+			/* Prepending the month */
+			gtk_tree_store_insert_before(store, &inserted_month, NULL, &month_iter);
+			gtk_tree_store_set(store, &inserted_month, 0, month, 1, NULL, 2, log->tm, -1);
+
+			gtk_tree_store_append(store, &insertion, &inserted_month);
+			gtk_tree_store_set(store, &insertion, 0, log_get_date(log), 1, log, -1);
+
+			return;
+		}
+
+	} while (gtk_tree_model_iter_next(model, &month_iter));
+
+	/* Appending the month */
+	gtk_tree_store_append(store, &inserted_month, NULL);
+	gtk_tree_store_set(store, &inserted_month, 0, month, 1, NULL, 2, log->tm, -1);
+
+	gtk_tree_store_append(store, &insertion, &inserted_month);
+	gtk_tree_store_set(store, &insertion, 0, log_get_date(log), 1, log, -1);
+}
+
+static void
+append_log_viewer_logs(PidginLogViewerEx *lv_ex, GList *logs)
+{
+	PidginLogViewer *lv = lv_ex->lv;
+	GList *list;
+
+	logs = g_list_sort(logs, purple_log_compare);
+
+	for (list = logs; list != NULL; list = g_list_next(list))
+		insert_log_viewer_log(lv_ex, list->data);
+
+	lv->logs = g_list_concat(lv->logs, logs);
+	lv->logs = g_list_sort(lv->logs, purple_log_compare);
+}
+
+static void
+pidgin_log_done_cb(_pidgin_log_data *pidgin_log_data)
+{
+	if (pidgin_log_data->is_window_open) {
+		PidginLogViewerEx *lv_ex = pidgin_log_data->log_viewer;
+		PidginLogViewer *lv = lv_ex->lv;
+
+		gtk_widget_hide(lv_ex->list_bar);
+
+		if (lv->logs == NULL) {
+			/* No logs were found. */
+			const gchar *log_preferences = NULL;
+
+			if (lv_ex->type == PURPLE_LOG_SYSTEM) {
+				if (!purple_prefs_get_bool("/purple/logging/log_system"))
+					log_preferences = _("System events will only be logged if "
+						"the \"Log all status changes to system log\" "
+						"preference is enabled.");
+			} else if (lv_ex->type == PURPLE_LOG_IM) {
+				if (!purple_prefs_get_bool("/purple/logging/log_ims"))
+					log_preferences = _("Instant messages will only be logged "
+					"if the \"Log all instant messages\" "
+					"preference is enabled.");
+			} else if (lv_ex->type == PURPLE_LOG_CHAT) {
+				if (!purple_prefs_get_bool("/purple/logging/log_chats"))
+					log_preferences = _("Chats will only be logged if the "
+						"\"Log all chats\" preference is enabled.");
+			}
+
+			purple_notify_info(NULL, gtk_window_get_title(GTK_WINDOW(lv->window)),
+				_("No logs were found"), log_preferences);
+			gtk_dialog_response(GTK_DIALOG(lv->window), GTK_RESPONSE_CLOSE);
+		} else if (!lv_ex->selected)
+			select_first_log(lv_ex);
+	}
+
+	pidgin_log_data_free(pidgin_log_data);
+}
+
+static void
+pidgin_log_viewer_update_list_bar(_pidgin_log_data *pidgin_log_data)
+{
+	if (pidgin_log_data->is_window_open) {
+		PidginLogViewerEx *lv_ex = pidgin_log_data->log_viewer;
+		gdouble fraction;
+
+		fraction = (gdouble) pidgin_log_data->count /
+			(gdouble) pidgin_log_data->total;
+		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(lv_ex->list_bar), 1.0 - fraction);
+	}
+}
+
+static void
+pidgin_log_viewer_update_search_bar(_pidgin_log_data *pidgin_log_data)
+{
+	if (pidgin_log_data->is_window_open) {
+		PidginLogViewerEx *lv_ex = pidgin_log_data->log_viewer;
+		gdouble fraction;
+
+		fraction = (gdouble) pidgin_log_data->count /
+			(gdouble) pidgin_log_data->total;
+		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(lv_ex->search_bar), 1.0 - fraction);
+	}
+}
+
+static void
+pidgin_log_size_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_pidgin_log_data *pidgin_log_data = userdata;
+	GError *err = NULL;
+	gssize log_size;
+
+	log_size = purple_log_get_total_size_finish(res, &err);
+	pidgin_log_data->count--;
+
+	if (log_size < 0) {
+		if (err->code != G_IO_ERROR_CANCELLED)
+			purple_debug_error("gtklog", "Error getting total log size: %s\n",
+				err->message);
+	} else if (pidgin_log_data->is_window_open) {
+		PidginLogViewerEx *lv_ex = pidgin_log_data->log_viewer;
+		gchar *sz_txt, *text;
+		gsize total;
+
+		total = lv_ex->size;
+		total += log_size;
+
+		lv_ex->size = total;
+
+		sz_txt = purple_str_size_to_units(total);
+		text = g_strdup_printf("<span weight='bold'>%s</span> %s",
+			_("Total log size:"), sz_txt);
+
+		gtk_label_set_markup(GTK_LABEL(lv_ex->lv->size_label), text);
+
+		g_free(sz_txt);
+		g_free(text);
+	}
+
+	g_clear_error(&err);
+	pidgin_log_viewer_update_list_bar(pidgin_log_data);
+
+	if (pidgin_log_data->count < 1)
+		pidgin_log_done_cb(pidgin_log_data);
+}
+
+static void
+pidgin_log_list_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_pidgin_log_data *pidgin_log_data = userdata;
+	GError *err = NULL;
+	GList *list;
+
+	list = purple_log_get_logs_finish(res, &err);
+	pidgin_log_data->count--;
+
+	if (list == NULL) {
+		if (err != NULL && err->code != G_IO_ERROR_CANCELLED)
+			purple_debug_error("gtklog", "Error getting logs: %s\n", err->message);
+	} else if (pidgin_log_data->is_window_open)
+		append_log_viewer_logs(pidgin_log_data->log_viewer, list);
+
+	g_clear_error(&err);
+	pidgin_log_viewer_update_search_bar(pidgin_log_data);
+
+	if (pidgin_log_data->count < 1)
+		pidgin_log_done_cb(pidgin_log_data);
+}
+
+static void
+pidgin_log_system_list_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_pidgin_log_data *pidgin_log_data = userdata;
+	GError *err = NULL;
+	GList *list;
+
+	list = purple_log_get_system_logs_finish(res, &err);
+	pidgin_log_data->count--;
+
+	if (list == NULL) {
+		if (err != NULL && err->code != G_IO_ERROR_CANCELLED)
+			purple_debug_error("gtklog", "Error getting system logs: %s\n", err->message);
+	} else if (pidgin_log_data->is_window_open)
+		append_log_viewer_logs(pidgin_log_data->log_viewer, list);
+
+	g_clear_error(&err);
+	pidgin_log_viewer_update_search_bar(pidgin_log_data);
+
+	if (pidgin_log_data->count < 1)
+		pidgin_log_done_cb(pidgin_log_data);
+}
+
+void
+pidgin_log_show(PurpleLogType type, const gchar *buddyname, PurpleAccount *account)
+{
+	log_viewer_hash_t *ht;
+	_pidgin_log_data *pidgin_log_data;
+	PidginLogViewerEx *lv_ex;
 	GdkPixbuf *prpl_icon;
+	GtkWidget *image;
+	GCancellable *cancel;
+	const gchar *name;
+	gchar *title;
 
-	g_return_if_fail(account != NULL);
-	g_return_if_fail(buddyname != NULL);
+	if (type != PURPLE_LOG_IM) {
+		g_return_if_fail(account != NULL);
+		g_return_if_fail(buddyname != NULL);
+	}
 
-	ht = g_new0(struct log_viewer_hash_t, 1);
+	name = buddyname;
+	ht = g_new0(log_viewer_hash_t, 1);
 
 	ht->type = type;
 	ht->buddyname = g_strdup(buddyname);
 	ht->account = account;
 
-	if (log_viewers == NULL) {
+	if (log_viewers == NULL)
 		log_viewers = g_hash_table_new(log_viewer_hash, log_viewer_equal);
-	} else if ((lv = g_hash_table_lookup(log_viewers, ht))) {
-		gtk_window_present(GTK_WINDOW(lv->window));
+	else if ((lv_ex = g_hash_table_lookup(log_viewers, ht)) != NULL) {
+		gtk_window_present(GTK_WINDOW(lv_ex->lv->window));
+
 		g_free(ht->buddyname);
 		g_free(ht);
+
 		return;
 	}
 
 	if (type == PURPLE_LOG_CHAT) {
-		PurpleChat *chat;
+		PurpleChat *chat = purple_blist_find_chat(account, buddyname);
 
-		chat = purple_blist_find_chat(account, buddyname);
 		if (chat != NULL)
 			name = purple_chat_get_name(chat);
 
 		title = g_strdup_printf(_("Conversations in %s"), name);
 	} else {
-		PurpleBuddy *buddy;
+		PurpleBuddy *buddy = purple_find_buddy(account, buddyname);
 
-		buddy = purple_find_buddy(account, buddyname);
 		if (buddy != NULL)
 			name = purple_buddy_get_contact_alias(buddy);
 
 		title = g_strdup_printf(_("Conversations with %s"), name);
 	}
 
+	pidgin_log_data = g_new0(_pidgin_log_data, 1);
+	pidgin_log_data->is_window_open = TRUE;
+	pidgin_log_data->count = 2;
+
 	prpl_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_MEDIUM);
+	image = gtk_image_new_from_pixbuf(prpl_icon);
+	lv_ex = pidgin_log_data->log_viewer =
+		display_log_viewer_nonblocking(ht, title, image, TRUE);
 
-	display_log_viewer(ht, purple_log_get_logs(type, buddyname, account),
-			title, gtk_image_new_from_pixbuf(prpl_icon),
-			purple_log_get_total_size(type, buddyname, account));
+	g_free(title);
 
-	if (prpl_icon)
+	if (prpl_icon != NULL)
 		g_object_unref(prpl_icon);
-	g_free(title);
+
+	pidgin_log_data->destroy_handler_id = g_signal_connect_swapped(lv_ex->lv->window,
+		"destroy", G_CALLBACK(pidgin_window_destroy_cb), pidgin_log_data);
+
+	cancel = g_cancellable_new();
+	pidgin_log_viewer_ex_set_list_cancel(lv_ex, cancel);
+
+	purple_log_get_logs_async(type, buddyname, account, G_PRIORITY_DEFAULT,
+		cancel, pidgin_log_list_cb, pidgin_log_data);
+	purple_log_get_total_size_async(type, buddyname, account, G_PRIORITY_DEFAULT,
+		cancel, pidgin_log_size_cb, pidgin_log_data);
+
+	g_object_unref(cancel);
 }
 
-void pidgin_log_show_contact(PurpleContact *contact) {
-	struct log_viewer_hash_t *ht;
+void
+pidgin_log_show_contact(PurpleContact *contact)
+{
+	log_viewer_hash_t *ht;
+	_pidgin_log_data *pidgin_log_data;
 	PurpleBlistNode *child;
-	PidginLogViewer *lv = NULL;
-	GList *logs = NULL;
+	PidginLogViewerEx *lv_ex;
+	GCancellable *cancel;
 	GdkPixbuf *pixbuf;
 	GtkWidget *image;
-	const char *name = NULL;
-	char *title;
-	int total_log_size = 0;
+	const gchar *name = NULL;
+	gchar *title;
 
 	g_return_if_fail(contact != NULL);
 
-	ht = g_new0(struct log_viewer_hash_t, 1);
+	ht = g_new0(log_viewer_hash_t, 1);
 	ht->type = PURPLE_LOG_IM;
 	ht->contact = contact;
 
 	if (log_viewers == NULL) {
 		log_viewers = g_hash_table_new(log_viewer_hash, log_viewer_equal);
-	} else if ((lv = g_hash_table_lookup(log_viewers, ht))) {
-		gtk_window_present(GTK_WINDOW(lv->window));
-		g_free(ht);
-		return;
-	}
+	} else if ((lv_ex = g_hash_table_lookup(log_viewers, ht)) != NULL) {
+		gtk_window_present(GTK_WINDOW(lv_ex->lv->window));
 
-	for (child = purple_blist_node_get_first_child((PurpleBlistNode*)contact) ;
-	     child != NULL ;
-	     child = purple_blist_node_get_sibling_next(child)) {
-		const char *buddy_name;
-		PurpleAccount *account;
+		g_free(ht);
 
-		if (!PURPLE_BLIST_NODE_IS_BUDDY(child))
-			continue;
-
-		buddy_name = purple_buddy_get_name((PurpleBuddy *)child);
-		account = purple_buddy_get_account((PurpleBuddy *)child);
-		logs = g_list_concat(purple_log_get_logs(PURPLE_LOG_IM, buddy_name, account), logs);
-		total_log_size += purple_log_get_total_size(PURPLE_LOG_IM, buddy_name, account);
+		return;
 	}
-	logs = g_list_sort(logs, purple_log_compare);
 
 	image = gtk_image_new();
 	pixbuf = gtk_widget_render_icon(image, PIDGIN_STOCK_STATUS_PERSON,
-					gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL), "GtkWindow");
-	if (pixbuf) {
+		gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL), "GtkWindow");
+
+	if (pixbuf != NULL) {
 		gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf);
 		g_object_unref(pixbuf);
 	} else {
@@ -798,37 +1450,81 @@ void pidgin_log_show_contact(PurpleConta
 	 * There is probably a better way to deal with this. */
 	if (name == NULL) {
 		if (contact->node.child != NULL && PURPLE_BLIST_NODE_IS_BUDDY(contact->node.child))
-			name = purple_buddy_get_contact_alias((PurpleBuddy *) contact->node.child);
+			name = purple_buddy_get_contact_alias(PURPLE_BUDDY(contact->node.child));
 		if (name == NULL)
 			name = "";
 	}
 
+	pidgin_log_data = g_new0(_pidgin_log_data, 1);
+	pidgin_log_data->is_window_open = TRUE;
+
 	title = g_strdup_printf(_("Conversations with %s"), name);
-	display_log_viewer(ht, logs, title, image, total_log_size);
+	lv_ex = pidgin_log_data->log_viewer = display_log_viewer_nonblocking(ht, title, image, TRUE);
+
 	g_free(title);
+
+	pidgin_log_data->destroy_handler_id = g_signal_connect_swapped(lv_ex->lv->window,
+		"destroy", G_CALLBACK(pidgin_window_destroy_cb), pidgin_log_data);
+
+	pidgin_log_data->count = pidgin_log_data->total = 0;
+	cancel = g_cancellable_new();
+	pidgin_log_viewer_ex_set_list_cancel(lv_ex, cancel);
+
+	for (child = contact->node.child; child != NULL; child = child->next) {
+		if (PURPLE_BLIST_NODE_IS_BUDDY(child)) {
+			PurpleBuddy *buddy = PURPLE_BUDDY(child);
+
+			pidgin_log_data->count = (pidgin_log_data->total += 2);
+
+			purple_log_get_logs_async(PURPLE_LOG_IM, buddy->name, buddy->account,
+				G_PRIORITY_DEFAULT, cancel, pidgin_log_list_cb, pidgin_log_data);
+			purple_log_get_total_size_async(PURPLE_LOG_IM, buddy->name, buddy->account,
+				G_PRIORITY_DEFAULT, cancel, pidgin_log_size_cb, pidgin_log_data);
+		}
+	}
+
+	g_object_unref(cancel);
 }
 
-void pidgin_syslog_show()
+void
+pidgin_syslog_show(void)
 {
-	GList *accounts = NULL;
-	GList *logs = NULL;
+	_pidgin_log_data *pidgin_log_data;
+	PurpleAccount *account;
+	GCancellable *cancel;
+	GList *accounts;
 
 	if (syslog_viewer != NULL) {
-		gtk_window_present(GTK_WINDOW(syslog_viewer->window));
+		gtk_window_present(GTK_WINDOW(syslog_viewer->lv->window));
 		return;
 	}
 
-	for(accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) {
+	pidgin_log_data = g_new0(_pidgin_log_data, 1);
+	pidgin_log_data->is_window_open = TRUE;
+	
+	syslog_viewer = pidgin_log_data->log_viewer = display_log_viewer_nonblocking(NULL,
+		_("System Log"), NULL, FALSE);
 
-		PurpleAccount *account = (PurpleAccount *)accounts->data;
-		if(purple_find_prpl(purple_account_get_protocol_id(account)) == NULL)
-			continue;
+	pidgin_log_data->destroy_handler_id = g_signal_connect_swapped(syslog_viewer->lv->window,
+		"destroy", G_CALLBACK(pidgin_window_destroy_cb), pidgin_log_data);
 
-		logs = g_list_concat(purple_log_get_system_logs(account), logs);
+	cancel = g_cancellable_new();
+	pidgin_log_viewer_ex_set_list_cancel(syslog_viewer, cancel);
+	pidgin_log_data->count = pidgin_log_data->total = 0;
+
+	for(accounts = purple_accounts_get_all(); accounts != NULL; accounts = g_list_next(accounts)) {
+		account = accounts->data;
+
+		if(purple_find_prpl(purple_account_get_protocol_id(account)) != NULL) {
+			pidgin_log_data->count++;
+			pidgin_log_data->total++;
+
+			purple_log_get_system_logs_async(account, G_PRIORITY_DEFAULT, cancel,
+				pidgin_log_system_list_cb, pidgin_log_data);
+		}
 	}
-	logs = g_list_sort(logs, purple_log_compare);
 
-	syslog_viewer = display_log_viewer(NULL, logs, _("System Log"), NULL, 0);
+	g_object_unref(cancel);
 }
 
 /****************************************************************************
@@ -843,17 +1539,16 @@ pidgin_log_get_handle(void)
 	return &handle;
 }
 
-void pidgin_log_init(void)
+void
+pidgin_log_init(void)
 {
 	void *handle = pidgin_log_get_handle();
 
 	purple_signal_register(handle, "log-displaying",
-	                     purple_marshal_VOID__POINTER_POINTER,
-	                     NULL, 2,
-	                     purple_value_new(PURPLE_TYPE_BOXED,
-	                                    "PidginLogViewer *"),
-	                     purple_value_new(PURPLE_TYPE_SUBTYPE,
-	                                    PURPLE_SUBTYPE_LOG));
+		purple_marshal_VOID__POINTER_POINTER,
+		NULL, 2,
+		purple_value_new(PURPLE_TYPE_BOXED, "PidginLogViewer *"),
+		purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_LOG));
 }
 
 void
============================================================
--- pidgin/gtklog.h	beebb1eb384edcdea9fb0073e342b726f7dd685b
+++ pidgin/gtklog.h	d2f68950e4d610a305124ca8efaeccc5f2b27839
@@ -32,54 +32,85 @@
 
 #include "account.h"
 
+/** @copydoc _PidginLogViewer */
 typedef struct _PidginLogViewer PidginLogViewer;
 
+/********************************************************
+ * DATA STRUCTURES **************************************
+ ********************************************************/
+
 /**
  * A GTK+ Log Viewer.  You can look at logs with it.
  */
 struct _PidginLogViewer {
-	GList *logs;                 /**< The list of logs viewed in this viewer   */
+	GList              *logs;       /**< The list of logs viewed in this viewer */
+	GtkWidget          *window;     /**< The viewer's window */
+	GtkTreeStore       *treestore;  /**< The treestore containing said logs */
+	GtkWidget          *treeview;   /**< The treeview representing said treestore */
+	GtkWidget          *imhtml;     /**< The imhtml to display said logs */
+	GtkWidget          *entry;      /**< The search entry, in which search terms are entered */
+	PurpleLogReadFlags flags;       /**< The most recently used log read flags */
+	gchar              *search;     /**< The string currently being searched for */
+	GtkWidget          *label;      /**< The label at the top of the log viewer */
+	GtkWidget          *size_label; /**< The label with total log size */
+};
 
-	GtkWidget        *window;    /**< The viewer's window                      */
-	GtkTreeStore     *treestore; /**< The treestore containing said logs       */
-	GtkWidget        *treeview;  /**< The treeview representing said treestore */
-	GtkWidget        *imhtml;    /**< The imhtml to display said logs          */
-	GtkWidget        *entry;     /**< The search entry, in which search terms
-	                              *   are entered                              */
-	PurpleLogReadFlags flags;      /**< The most recently used log flags         */
-	char             *search;    /**< The string currently being searched for  */
-	GtkWidget        *label;     /**< The label at the top of the log viewer   */
-};
+G_BEGIN_DECLS
 
+/**************************************************************************/
+/** @name Log Viewer Creators                                             */
+/**************************************************************************/
 
+/*@{*/
 
-void pidgin_log_show(PurpleLogType type, const char *buddyname, PurpleAccount *account);
+/**
+ * Displays the logs of a certain type for a buddy or chat on an account
+ *
+ * @param type:        The #PurpleLogType
+ * @param buddyname:   The buddy or chat name
+ * @param account:     The account
+ */
+void pidgin_log_show(PurpleLogType type, const gchar *buddyname, PurpleAccount *account);
+
+/**
+ * Displays the logs for one contact
+ *
+ * @param contact:     The #PurpleContact to display the logs of
+ */
 void pidgin_log_show_contact(PurpleContact *contact);
 
+/**
+ * Displays the system log
+ */
 void pidgin_syslog_show(void);
 
+/*@}*/
+
 /**************************************************************************/
 /** @name GTK+ Log Subsystem                                              */
 /**************************************************************************/
+
 /*@{*/
 
 /**
- * Initializes the GTK+ log subsystem.
+ * Initializes the GTK+ log subsystem
  */
 void pidgin_log_init(void);
 
 /**
- * Returns the GTK+ log subsystem handle.
+ * Gets the GTK+ log subsystem handle
  *
- * @return The GTK+ log subsystem handle.
+ * @return The GTK+ log subsystem handle
  */
 void *pidgin_log_get_handle(void);
 
 /**
- * Uninitializes the GTK+ log subsystem.
+ * Uninitializes the GTK+ log subsystem
  */
 void pidgin_log_uninit(void);
 
 /*@}*/
 
+G_END_DECLS
+
 #endif
============================================================
--- ChangeLog.API	044e3549da4591f8b8743864fafbcb1880b0ccb6
+++ ChangeLog.API	d1d23d41da22fafe590ba716ce132f2e7672aa45
@@ -1,5 +1,25 @@ Pidgin and Finch: The Pimpin' Penguin IM
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.8.0 (??/??/????):
+	libpurple:
+		Added:
+		* Non-blocking Logging API
+		   * purple_log_write_async, purple_log_write_finish
+		   * purple_log_read_async, purple_log_read_finish
+		   * purple_log_get_logs_async, purple_log_get_logs_finish
+		   * purple_log_get_log_sets_async, purple_log_get_log_sets_finish
+		   * purple_log_get_system_logs_async, purple_log_get_system_logs_finish
+		   * purple_log_get_size_async, purple_log_get_size_finish
+		   * purple_log_get_total_size_async, purple_log_get_total_size_finish
+		   * purple_log_get_activity_score_async, purple_log_get_activity_score_finish
+		   * purple_log_delete_async, purple_log_delete_finish
+		   * purple_log_common_writer_async, purple_log_common_writer_finish
+		   * purple_log_common_lister_async, purple_log_common_lister_finish
+		   * purple_log_common_total_sizer_async, purple_log_common_total_sizer_finish
+		   * purple_log_common_sizer_async, purple_log_common_sizer_finish
+		   * purple_log_common_deleter_async, purple_log_common_deleter_finish
+		   * purple_log_logger_get_all
+
 version 2.7.9 (??/??/????):
 	* No changes
 
============================================================
--- finch/plugins/gnthistory.c	3f06a038dfdd7cb19025a1b4fbd4788cae8f92bc
+++ finch/plugins/gnthistory.c	0ceb2703077587e3417212852dd423e97da2ec0b
@@ -39,168 +39,411 @@
 
 #define HISTORY_SIZE (4 * 1024)
 
-static void historize(PurpleConversation *c)
+typedef struct {
+	PurpleAccount *account;
+	PurpleConversation *conv;
+	PurpleLog *log;
+	PurpleLogReadFlags flags;
+	GCancellable *cancel;
+	const gchar *name;
+	const gchar *alias;
+	guint count;
+	gulong delete_handle;
+} _historize_callback_data;
+
+static void historize(PurpleConversation *);
+static gboolean _scroll_gnt_text_view_to_end(gpointer);
+static PurpleLog *get_last_log(GList *, PurpleLog *);
+static void historize_log_read_cb(GObject *, GAsyncResult *, gpointer);
+static void historize_log_list_cb(GObject *, GAsyncResult *, gpointer);
+static void historize_log_list_cb_done(_historize_callback_data *);
+static void historize_log_collector_cb(GObject *, GAsyncResult *, gpointer);
+static void historize_log_collector_cb_done(_historize_callback_data *);
+static void historize_stop(PurpleConversation *, gpointer);
+static void historize_log_free(_historize_callback_data *);
+static void history_prefs_check(PurplePlugin *);
+static void history_prefs_cb(const gchar *, PurplePrefType, gconstpointer, gpointer);
+static gboolean plugin_load(PurplePlugin *);
+static void init_plugin(PurplePlugin *);
+
+static void
+historize(PurpleConversation *c)
 {
+	_historize_callback_data *callback_data;
 	PurpleAccount *account = purple_conversation_get_account(c);
-	const char *name = purple_conversation_get_name(c);
-	PurpleConversationType convtype;
-	GList *logs = NULL;
-	const char *alias = name;
-	PurpleLogReadFlags flags;
-	char *history;
-	char *header;
-	PurpleMessageFlags mflag;
+	PurpleConversationType convtype = purple_conversation_get_type(c);
+	FinchConv *fc = FINCH_CONV(c);
+	const gchar *name = purple_conversation_get_name(c), *alias = name;
 
-	convtype = purple_conversation_get_type(c);
-	if (convtype == PURPLE_CONV_TYPE_IM) {
-		GSList *buddies;
-		GSList *cur;
-		FinchConv *fc = FINCH_CONV(c);
-		if (fc->list && fc->list->next) /* We were already in the middle of a conversation. */
-			return;
+	g_return_if_fail(fc != NULL);
+	g_return_if_fail(fc->list != NULL);
 
+	callback_data = g_new0(_historize_callback_data, 1);
+	callback_data->conv = c;
+	callback_data->alias = alias;
+	callback_data->name = name;
+	callback_data->account = account;
+	callback_data->cancel = g_cancellable_new();
+
+	if (convtype == PURPLE_CONV_TYPE_IM && fc->list->next == NULL) {
+		GSList *buddies, *cur;
+
 		/* If we're not logging, don't show anything.
 		 * Otherwise, we might show a very old log. */
-		if (!purple_prefs_get_bool("/purple/logging/log_ims"))
+		if (!purple_prefs_get_bool("/purple/logging/log_ims")) {
+			g_object_unref(callback_data->cancel);
+			g_free(callback_data);
+
 			return;
+		}
 
 		/* Find buddies for this conversation. */
 		buddies = purple_find_buddies(account, name);
 
 		/* If we found at least one buddy, save the first buddy's alias. */
 		if (buddies != NULL)
-			alias = purple_buddy_get_contact_alias((PurpleBuddy *)buddies->data);
+			alias = purple_buddy_get_contact_alias(PURPLE_BUDDY(buddies->data));
 
-		for (cur = buddies; cur != NULL; cur = cur->next) {
+		for (cur = buddies; cur != NULL; cur = g_slist_next(cur)) {
 			PurpleBlistNode *node = cur->data;
-			if ((node != NULL) &&
-					((purple_blist_node_get_sibling_prev(node) != NULL) ||
-						(purple_blist_node_get_sibling_next(node) != NULL))) {
+
+			if (node != NULL &&
+				(purple_blist_node_get_sibling_prev(node) != NULL ||
+				purple_blist_node_get_sibling_next(node) != NULL))
+			{
 				PurpleBlistNode *node2;
+				PurpleBlistNode *child = purple_blist_node_get_first_child(parent);
+				PurpleBlistNode *parent = purple_blist_node_get_parent(node);
 
-				alias = purple_buddy_get_contact_alias((PurpleBuddy *)node);
+				alias = purple_buddy_get_contact_alias(PURPLE_BUDDY(node));
 
 				/* We've found a buddy that matches this conversation.  It's part of a
 				 * PurpleContact with more than one PurpleBuddy.  Loop through the PurpleBuddies
 				 * in the contact and get all the logs. */
-				for (node2 = purple_blist_node_get_first_child(purple_blist_node_get_parent(node));
-						node2 != NULL ; node2 = purple_blist_node_get_sibling_next(node2)) {
-					logs = g_list_concat(
-							purple_log_get_logs(PURPLE_LOG_IM,
-								purple_buddy_get_name((PurpleBuddy *)node2),
-								purple_buddy_get_account((PurpleBuddy *)node2)),
-							logs);
+				for (node2 = child; node2 != NULL; node2 = purple_blist_node_get_sibling_next(node2))
+				{
+					PurpleBuddy *buddy = PURPLE_BUDDY(node2);
+
+					callback_data->count++;
+
+					purple_log_get_logs_async(PURPLE_LOG_IM, purple_buddy_get_name(buddy),
+						purple_buddy_get_account(buddy), G_PRIORITY_DEFAULT,
+						callback_data->cancel, historize_log_collector_cb, callback_data);
 				}
+
 				break;
 			}
 		}
-		g_slist_free(buddies);
 
-		if (logs == NULL)
-			logs = purple_log_get_logs(PURPLE_LOG_IM, name, account);
-		else
-			logs = g_list_sort(logs, purple_log_compare);
+		g_slist_free(buddies);
 	} else if (convtype == PURPLE_CONV_TYPE_CHAT) {
 		/* If we're not logging, don't show anything.
 		 * Otherwise, we might show a very old log. */
-		if (!purple_prefs_get_bool("/purple/logging/log_chats"))
+		if (!purple_prefs_get_bool("/purple/logging/log_chats")) {
+			g_object_unref(callback_data->cancel);
+			g_free(callback_data);
+
 			return;
+		}
 
-		logs = purple_log_get_logs(PURPLE_LOG_CHAT, name, account);
+		callback_data->count = 1;
+
+		purple_log_get_logs_async(PURPLE_LOG_CHAT, name, account, G_PRIORITY_DEFAULT,
+			callback_data->cancel, historize_log_list_cb, callback_data);
 	}
 
-	if (logs == NULL)
+	callback_data->delete_handle = purple_signal_connect(purple_conversations_get_handle(),
+		"deleting-conversation", c, PURPLE_CALLBACK(historize_stop), callback_data);
+}
+
+static gboolean
+_scroll_gnt_text_view_to_end(gpointer userdata)
+{
+	GntTextView *view = GNT_TEXT_VIEW(userdata);
+
+	gnt_text_view_scroll(view, 0);
+	g_object_unref(view);
+
+	return FALSE;
+}
+
+static PurpleLog *
+get_last_log(GList *list, PurpleLog *last_log)
+{
+	GList *node;
+
+	if (last_log == NULL) {
+		last_log = list->data;
+		node = g_list_next(list);
+	} else
+		node = list;
+
+	for( ; node; node = g_list_next(node))
+		if (purple_log_compare(last_log, node->data) > 0) {
+			purple_log_free(last_log);
+			last_log = node->data;
+		} else
+			purple_log_free(node->data);
+
+	g_list_free(list);
+
+	return last_log;
+}
+
+static void
+historize_log_read_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_historize_callback_data *callback_data = userdata;
+	PurpleConversation *c = callback_data->conv;
+	PurpleLog *log = callback_data->log;
+	PurpleMessageFlags options;
+	FinchConv *fc = FINCH_CONV(callback_data->conv);
+	GntTextView *view = GNT_TEXT_VIEW(fc->tv);
+	GError *err = NULL;
+	GString *buffer = view->string;
+	const gchar *header_date;
+	gchar *text, *escaped_alias, *header, *full_header, *text_backup;
+
+	if (res == NULL) {
+		callback_data->count--;
+
+		if (callback_data->count < 1)
+			historize_log_free(callback_data);
+
 		return;
+	}
 
-	mflag = PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_DELAYED;
-	history = purple_log_read((PurpleLog*)logs->data, &flags);
+	options = PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_DELAYED;
 
-	header = g_strdup_printf(_("<b>Conversation with %s on %s:</b><br>"), alias,
-			purple_date_format_full(localtime(&((PurpleLog *)logs->data)->time)));
-	purple_conversation_write(c, "", header, mflag, time(NULL));
+	if (buffer != NULL && *buffer->str) {
+		text_backup = strdup(buffer->str);
+		gnt_text_view_clear(view);
+	} else
+		text_backup = NULL;
+
+	if (buffer->len > 0)
+		purple_conversation_write(c, "", "<hr>", options, time(NULL));
+
+	if (log->tm)
+		header_date = purple_date_format_full(log->tm);
+	else
+		header_date = purple_date_format_full(localtime(&log->time));
+
+	escaped_alias = g_markup_escape_text(callback_data->alias, -1);
+	full_header = g_strdup_printf(_("Conversation with %s on %s"), escaped_alias, header_date);
+	header = g_strdup_printf("<b>%s:</b><br>", full_header);
+
+	purple_conversation_write(c, "", header, options, time(NULL));
+
 	g_free(header);
+	g_free(escaped_alias);
+	g_free(full_header);
 
-	if (flags & PURPLE_LOG_READ_NO_NEWLINE)
-		purple_str_strip_char(history, '\n');
-	purple_conversation_write(c, "", history, mflag, time(NULL));
-	g_free(history);
+	text = purple_log_read_finish(log, res, &err);
 
-	purple_conversation_write(c, "", "<hr>", mflag, time(NULL));
+	if (text == NULL)
+		text = err->message;
+	else
+		g_strchomp(text);
 
-	g_list_foreach(logs, (GFunc)purple_log_free, NULL);
-	g_list_free(logs);
+	if (callback_data->flags & PURPLE_LOG_READ_NO_NEWLINE)
+		purple_str_strip_char(text, '\n');
+
+	purple_conversation_write(c, "", text, options, time(NULL));
+	purple_conversation_write(c, "", "<hr>", options, time(NULL));
+
+	if (text_backup != NULL) {
+		purple_conversation_write(c, "", text_backup, PURPLE_MESSAGE_NO_LOG, time(NULL));
+		g_free(text_backup);
+	}
+
+	g_idle_add(_scroll_gnt_text_view_to_end, g_object_ref(view));
+
+	g_clear_error(&err);
+
+	callback_data->count--;
+
+	if (callback_data->count < 1)
+		historize_log_free(callback_data);
 }
 
 static void
+historize_log_list_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_historize_callback_data *callback_data = userdata;
+	GList *list;
+	GError *err = NULL;
+
+	if (res == NULL) {
+		callback_data->count--;
+
+		if (callback_data->count < 1)
+			historize_log_list_cb_done(callback_data);
+
+		return;
+	}
+
+	list = purple_log_get_logs_finish(res, &err);
+
+	if (list != NULL)
+		callback_data->log = get_last_log(list, callback_data->log);
+	else if (err != NULL && err->code != G_IO_ERROR_CANCELLED)
+		purple_debug_error("gnthistory", "%s\n", err->message);
+
+	g_clear_error(&err);
+
+	callback_data->count--;
+
+	if (callback_data->count < 1)
+		historize_log_list_cb_done(callback_data);
+}
+
+static void
+historize_log_list_cb_done(_historize_callback_data *callback_data)
+{
+	callback_data->count++;
+
+	if (callback_data->log != NULL)
+		purple_log_read_async(callback_data->log, &callback_data->flags,
+			G_PRIORITY_DEFAULT, callback_data->cancel, historize_log_read_cb,
+			callback_data);
+	else
+		historize_log_read_cb(NULL, NULL, callback_data);
+}
+
+static void
+historize_log_collector_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_historize_callback_data *callback_data = userdata;
+	GList *list;
+	GError *err = NULL;
+
+	list = purple_log_get_logs_finish(res, &err);
+
+	if (list != NULL)
+		callback_data->log = get_last_log(list, callback_data->log);
+	else if (err != NULL && err->code != G_IO_ERROR_CANCELLED)
+		purple_debug_error("gnthistory", "%s\n", err->message);
+
+	g_clear_error(&err);
+
+	callback_data->count--;
+
+	if (callback_data->count < 1)
+		historize_log_collector_cb_done(userdata);
+}
+
+static void
+historize_log_collector_cb_done(_historize_callback_data *callback_data)
+{
+	callback_data->count++;
+
+	if (callback_data->log == NULL)
+		purple_log_get_logs_async(PURPLE_LOG_IM, callback_data->name,
+			callback_data->account, G_PRIORITY_DEFAULT, callback_data->cancel,
+			historize_log_list_cb, callback_data);
+	else
+		historize_log_list_cb(NULL, NULL, callback_data);
+}
+
+static void
+historize_stop(PurpleConversation *c, gpointer userdata)
+{
+	_historize_callback_data *callback_data = userdata;
+
+	if (callback_data->cancel != NULL)
+		g_cancellable_cancel(callback_data->cancel);
+
+	purple_signal_disconnect(purple_conversations_get_handle(), "deleting-conversation",
+		c, PURPLE_CALLBACK(historize_stop));
+}
+
+static void
+historize_log_free(_historize_callback_data *callback_data)
+{
+	if (callback_data->log != NULL)
+		purple_log_free(callback_data->log);
+
+	// Why on earth does pidgin give us gulong to refer to our connected signal if it
+	// offers no API to disconnect it with!?
+	purple_signal_disconnect(purple_conversations_get_handle(), "deleting-conversation",
+		callback_data->conv, PURPLE_CALLBACK(historize_stop));
+
+	g_object_unref(callback_data->cancel);
+
+	g_free(callback_data);
+}
+
+static void
 history_prefs_check(PurplePlugin *plugin)
 {
 	if (!purple_prefs_get_bool("/purple/logging/log_ims") &&
-	    !purple_prefs_get_bool("/purple/logging/log_chats"))
+		!purple_prefs_get_bool("/purple/logging/log_chats"))
 	{
+		PurpleRequestField *field = purple_request_field_list_new("/purple/logging/format", _("Log format"));
 		PurpleRequestFields *fields = purple_request_fields_new();
-		PurpleRequestFieldGroup *group;
-		PurpleRequestField *field;
+		PurpleRequestFieldGroup *group = purple_request_field_group_new(_("Logging"));
+		GList *list = purple_log_logger_get_options();
+		const gchar *system = purple_prefs_get_string("/purple/logging/format");
+		gint iter;
 		struct {
-			const char *pref;
-			const char *label;
+			const gchar *pref;
+			const gchar *label;
 		} prefs[] = {
 			{"/purple/logging/log_ims", N_("Log IMs")},
 			{"/purple/logging/log_chats", N_("Log chats")},
 			{NULL, NULL}
 		};
-		int iter;
-		GList *list = purple_log_logger_get_options();
-		const char *system = purple_prefs_get_string("/purple/logging/format");
 
-		group = purple_request_field_group_new(_("Logging"));
+		for ( ; list != NULL; list = g_list_delete_link(list, list)) {
+			const gchar *label = _(list->data);
 
-		field = purple_request_field_list_new("/purple/logging/format", _("Log format"));
-		while (list) {
-			const char *label = _(list->data);
 			list = g_list_delete_link(list, list);
 			purple_request_field_list_add_icon(field, label, NULL, list->data);
+
 			if (system && strcmp(system, list->data) == 0)
 				purple_request_field_list_add_selected(field, label);
-			list = g_list_delete_link(list, list);
 		}
+
 		purple_request_field_group_add_field(group, field);
 
 		for (iter = 0; prefs[iter].pref; iter++) {
 			field = purple_request_field_bool_new(prefs[iter].pref, _(prefs[iter].label),
-						purple_prefs_get_bool(prefs[iter].pref));
+				purple_prefs_get_bool(prefs[iter].pref));
+
 			purple_request_field_group_add_field(group, field);
 		}
 
 		purple_request_fields_add_group(fields, group);
 
 		purple_request_fields(plugin, NULL, _("History Plugin Requires Logging"),
-				      _("Logging can be enabled from Tools -> Preferences -> Logging.\n\n"
-				      "Enabling logs for instant messages and/or chats will activate "
-				      "history for the same conversation type(s)."),
-				      fields,
-				      _("OK"), G_CALLBACK(finch_request_save_in_prefs),
-				      _("Cancel"), NULL,
-				      NULL, NULL, NULL, plugin);
+			_("Logging can be enabled from Tools -> Preferences -> Logging.\n\n"
+			"Enabling logs for instant messages and/or chats will activate "
+			"history for the same conversation type(s)."),
+			fields,
+			_("OK"), G_CALLBACK(finch_request_save_in_prefs),
+			_("Cancel"), NULL,
+			NULL, NULL, NULL, plugin);
 	}
 }
 
-static void history_prefs_cb(const char *name, PurplePrefType type,
-							 gconstpointer val, gpointer data)
+static void
+history_prefs_cb(const gchar *name, PurplePrefType type, gconstpointer val, gpointer data)
 {
-	history_prefs_check((PurplePlugin *)data);
+	history_prefs_check((PurplePlugin *) data);
 }
 
 static gboolean
 plugin_load(PurplePlugin *plugin)
 {
 	purple_signal_connect(purple_conversations_get_handle(),
-						"conversation-created",
-						plugin, PURPLE_CALLBACK(historize), NULL);
+		"conversation-created",
+		plugin, PURPLE_CALLBACK(historize), NULL);
 
 	purple_prefs_connect_callback(plugin, "/purple/logging/log_ims",
-								history_prefs_cb, plugin);
+		history_prefs_cb, plugin);
 	purple_prefs_connect_callback(plugin, "/purple/logging/log_chats",
-								history_prefs_cb, plugin);
+		history_prefs_cb, plugin);
 
 	history_prefs_check(plugin);
 
============================================================
--- pidgin/Makefile.mingw	867b6c76a7ab966075f38718bb81a9ce5308a473
+++ pidgin/Makefile.mingw	730c0aa339502a6c46952ca9da4ca355e1bd84d5
@@ -116,6 +116,7 @@ PIDGIN_LIBS =	\
 		-lintl \
 		-lglib-2.0 \
 		-lgobject-2.0 \
+		-lgio-2.0 \
 		-lgthread-2.0 \
 		-lpurple \
 		-lz \
============================================================
--- pidgin/plugins/Makefile.mingw	eb4f0847480f78f03e28cee4247f552320baa6ec
+++ pidgin/plugins/Makefile.mingw	eb914f003734a16f54e0984e598b182ec201bbcd
@@ -44,6 +44,7 @@ LIBS =			-lgtk-win32-2.0 \
 LIBS =			-lgtk-win32-2.0 \
 			-lglib-2.0 \
 			-lgdk-win32-2.0 \
+			-lgio-2.0 \
 			-lgobject-2.0 \
 			-lgmodule-2.0 \
 			-lgdk_pixbuf-2.0 \
============================================================
--- libpurple/Makefile.mingw	c00779a8a36018e900636774f2d2e7fcfe2890a9
+++ libpurple/Makefile.mingw	e15167af874d0b95fb51b36f35d9748cbf8320b2
@@ -96,6 +96,7 @@ LIBS =	\
 ## LIBRARIES
 ##
 LIBS =	\
+		-lgio-2.0 \
 		-lglib-2.0 \
 		-lgthread-2.0 \
 		-lgobject-2.0 \
============================================================
--- finch/gntlog.c	aaa0c27889f1c7cba351913d53056e2e1b9aa76f
+++ finch/gntlog.c	ed7282419ae6ff1bd595c802a14a6f00bd3d55ed
@@ -31,6 +31,7 @@
 #include <gntbutton.h>
 #include <gntentry.h>
 #include <gntlabel.h>
+#include <gntprogressbar.h>
 #include <gnttextview.h>
 #include <gnttree.h>
 #include <gntwindow.h>
@@ -44,41 +45,138 @@
 
 #include "gntlog.h"
 
-static GHashTable *log_viewers = NULL;
-static void populate_log_tree(FinchLogViewer *lv);
-static FinchLogViewer *syslog_viewer = NULL;
+// FinchLogViewer members are stored here to avoid an ABI break, will be moved back in 3.x
+typedef struct {
+	FinchLogViewer     *lv;            /**< The rest of the struct */
+	gsize              size;           /**< The actual value of the total log size */
+	GntWidget          *size_label;    /**< The label of the size of the log */
+	GntWidget          *list_bar;      /**< The progress bar */
+	GntWidget          *search_bar;    /**< The progess bar for searches */
 
-struct log_viewer_hash_t {
-	PurpleLogType type;
-	char *username;
+	GCancellable       *read_cancel;   /**< A GCancellable to stop any reads */
+	GCancellable       *search_cancel; /**< A GCancellable to stop any searches */
+	GCancellable       *list_cancel;   /**< A GCancellable to stop any folder listings */
+
+	PurpleLogType      type;           /**< The log type of the window */
+} FinchLogViewerEx;
+
+typedef struct {
+	FinchLogViewerEx *log_viewer;
+	PurpleLog *log;
+
+	gboolean is_window_open;
+	gulong destroy_handler_id;
+
+	guint count;
+	guint total;
+} _finch_log_data;
+
+typedef struct {
+	_finch_log_data *log_data;
+	PurpleLog *log;
+} _finch_search_callback_data;
+
+typedef struct {
 	PurpleAccount *account;
 	PurpleContact *contact;
-};
+	PurpleLogType type;
+	gchar *username;
+} log_viewer_hash_t;
 
-static guint log_viewer_hash(gconstpointer data)
+
+static void finch_log_data_free(_finch_log_data *);
+static void finch_window_destroy_cb(_finch_log_data *);
+static void finch_log_viewer_ex_set_read_cancel(FinchLogViewerEx *, GCancellable *);
+static void finch_log_viewer_ex_set_search_cancel(FinchLogViewerEx *, GCancellable *);
+static void finch_log_viewer_ex_set_list_cancel(FinchLogViewerEx *, GCancellable *);
+static guint log_viewer_hash(gconstpointer);
+static gboolean log_viewer_equal(gconstpointer, gconstpointer);
+static const gchar *log_get_date(PurpleLog *);
+static void finch_log_search_done_cb(_finch_log_data *);
+static void finch_log_search_cb(GObject *, GAsyncResult *, gpointer);
+static void search_cb(GntWidget *, FinchLogViewerEx *);
+static void destroy_cb(GntWidget *, log_viewer_hash_t *);
+static void finch_log_read_cb(GObject *, GAsyncResult *, gpointer);
+static void log_select_cb(GntWidget *, gpointer, gpointer, FinchLogViewerEx *);
+static void populate_log_tree(FinchLogViewerEx *);
+static FinchLogViewerEx *display_log_viewer_nonblocking(log_viewer_hash_t *,
+	const gchar *, gboolean);
+static void finch_log_done_cb(_finch_log_data *);
+static void finch_log_viewer_update_list_bar(_finch_log_data *);
+static void finch_log_viewer_update_search_bar(_finch_log_data *);
+static void finch_log_size_cb(GObject *, GAsyncResult *, gpointer);
+static void finch_log_list_cb(GObject *, GAsyncResult *, gpointer);
+static void finch_log_system_list_cb(GObject *, GAsyncResult *, gpointer);
+static void finch_log_sets_cb(GObject *, GAsyncResult *, gpointer);
+
+
+static GHashTable *log_viewers = NULL;
+static FinchLogViewerEx *syslog_viewer = NULL;
+
+
+static void
+finch_log_viewer_ex_set_read_cancel(FinchLogViewerEx *lv_ex, GCancellable *cancel)
 {
-	const struct log_viewer_hash_t *viewer = data;
+	if (lv_ex->read_cancel != NULL) {
+		g_cancellable_cancel(lv_ex->read_cancel);
+		g_object_unref(lv_ex->read_cancel);
+	}
 
+	if (cancel != NULL)
+		g_object_ref(cancel);
+
+	lv_ex->read_cancel = cancel;
+}
+
+static void
+finch_log_viewer_ex_set_search_cancel(FinchLogViewerEx *lv_ex, GCancellable *cancel)
+{
+	if (lv_ex->search_cancel != NULL) {
+		g_cancellable_cancel(lv_ex->search_cancel);
+		g_object_unref(lv_ex->search_cancel);
+	}
+
+	if (cancel != NULL)
+		g_object_ref(cancel);
+
+	lv_ex->search_cancel = cancel;
+}
+
+static void
+finch_log_viewer_ex_set_list_cancel(FinchLogViewerEx *lv_ex, GCancellable *cancel)
+{
+	if (lv_ex->list_cancel != NULL) {
+		g_cancellable_cancel(lv_ex->list_cancel);
+		g_object_unref(lv_ex->list_cancel);
+	}
+
+	if (cancel != NULL)
+		g_object_ref(cancel);
+
+	lv_ex->list_cancel = cancel;
+}
+
+static guint
+log_viewer_hash(gconstpointer data)
+{
+	const log_viewer_hash_t *viewer = data;
+
 	if (viewer->contact != NULL)
 		return g_direct_hash(viewer->contact);
 
-	if (viewer->account) {
+	if (viewer->account)
 		return g_str_hash(viewer->username) +
 			g_str_hash(purple_account_get_username(viewer->account));
-	}
 
 	return g_direct_hash(viewer);
 }
 
-static gboolean log_viewer_equal(gconstpointer y, gconstpointer z)
+static gboolean
+log_viewer_equal(gconstpointer y, gconstpointer z)
 {
-	const struct log_viewer_hash_t *a, *b;
-	int ret;
-	char *normal;
+	const log_viewer_hash_t *a = y, *b = z;
+	gint ret;
 
-	a = y;
-	b = z;
-
 	if (a->contact != NULL) {
 		if (b->contact != NULL)
 			return (a->contact == b->contact);
@@ -90,7 +188,7 @@ static gboolean log_viewer_equal(gconstp
 	}
 
 	if (a->username && b->username) {
-		normal = g_strdup(purple_normalize(a->account, a->username));
+		gchar *normal = g_strdup(purple_normalize(a->account, a->username));
 		ret = (a->account == b->account) &&
 			!strcmp(normal, purple_normalize(b->account, b->username));
 		g_free(normal);
@@ -101,7 +199,8 @@ static gboolean log_viewer_equal(gconstp
 	return ret;
 }
 
-static const char *log_get_date(PurpleLog *log)
+static const gchar *
+log_get_date(PurpleLog *log)
 {
 	if (log->tm)
 		return purple_date_format_full(log->tm);
@@ -109,51 +208,145 @@ static const char *log_get_date(PurpleLo
 		return purple_date_format_full(localtime(&log->time));
 }
 
-static void search_cb(GntWidget *button, FinchLogViewer *lv)
+static void
+finch_log_data_free(_finch_log_data *data)
 {
-	const char *search_term = gnt_entry_get_text(GNT_ENTRY(lv->entry));
+	if (data->destroy_handler_id > 0)
+		g_signal_handler_disconnect(data->log_viewer->lv->window,
+			data->destroy_handler_id);
+
+	g_free(data);
+}
+
+static void
+finch_window_destroy_cb(_finch_log_data *data)
+{
+	data->destroy_handler_id = 0;
+	data->is_window_open = FALSE;
+}
+
+static void
+finch_log_search_done_cb(_finch_log_data *finch_log_data)
+{
+//	if (finch_log_data->is_window_open) {
+//		FinchLogViewerEx *lv_ex = finch_log_data->log_viewer;
+//
+//		gnt_widget_hide(lv_ex->search_bar);
+//	}
+
+	finch_log_data_free(finch_log_data);
+}
+
+static void
+finch_log_search_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_finch_search_callback_data *callback_data = userdata;
+	_finch_log_data *finch_log_data = callback_data->log_data;
+	PurpleLog *log = callback_data->log;
+	GError *err = NULL;
+	gchar *read;
+
+	read = purple_log_read_finish(log, res, &err);
+	finch_log_data->count--;
+
+	if (read == NULL) {
+		if (err->code != G_IO_ERROR_CANCELLED)
+			purple_debug_error("gntlog", "%s", err->message);
+	} else if (finch_log_data->is_window_open) {
+		FinchLogViewer *lv = finch_log_data->log_viewer->lv;
+
+		if (*read && purple_strcasestr(read, lv->search))
+			gnt_tree_add_row_last(GNT_TREE(lv->tree), log,
+				gnt_tree_create_row(GNT_TREE(lv->tree),
+					log_get_date(log)),
+				NULL);
+	}
+
+	g_clear_error(&err);
+	finch_log_viewer_update_search_bar(finch_log_data);
+
+	if (finch_log_data->count < 1)
+		finch_log_search_done_cb(finch_log_data);
+
+	g_free(callback_data);
+}
+
+static void
+search_cb(GntWidget *button, FinchLogViewerEx *lv_ex)
+{
+	_finch_search_callback_data *callback_data;
+	_finch_log_data *finch_log_data;
+	FinchLogViewer *lv = lv_ex->lv;
+	GCancellable *cancel;
 	GList *logs;
+	const gchar *search_term = gnt_entry_get_text(GNT_ENTRY(lv->entry));
+	guint length;
 
 	if (!(*search_term)) {
-		/* reset the tree */
-		gnt_tree_remove_all(GNT_TREE(lv->tree));
+		/* Reset the tree, make sure we clear the search term first so that
+		   log_select_cb knows how to tell the months from the individual logs
+		 */
 		g_free(lv->search);
 		lv->search = NULL;
-		populate_log_tree(lv);
+
+		finch_log_viewer_ex_set_search_cancel(lv_ex, NULL);
+		gnt_tree_remove_all(GNT_TREE(lv->tree));
+		populate_log_tree(lv_ex);
+
 		return;
 	}
 
-	if (lv->search != NULL && !strcmp(lv->search, search_term)) {
+	if (lv->search != NULL && !strcmp(lv->search, search_term))
 		return;
-	}
 
 	g_free(lv->search);
 	lv->search = g_strdup(search_term);
 
+	length = g_list_length(lv->logs);
+
+	if (length < 1)
+		return;
+
 	gnt_tree_remove_all(GNT_TREE(lv->tree));
 	gnt_text_view_clear(GNT_TEXT_VIEW(lv->text));
 
-	for (logs = lv->logs; logs != NULL; logs = logs->next) {
-		char *read = purple_log_read((PurpleLog*)logs->data, NULL);
-		if (read && *read && purple_strcasestr(read, search_term)) {
-			PurpleLog *log = logs->data;
+	finch_log_data = g_new0(_finch_log_data, 1);
+	finch_log_data->is_window_open = TRUE;
+	finch_log_data->log_viewer = lv_ex;
+	finch_log_data->count = finch_log_data->total = length;
+	finch_log_data->destroy_handler_id = g_signal_connect_swapped(lv->window,
+		"destroy", G_CALLBACK(finch_window_destroy_cb), finch_log_data);
 
-			gnt_tree_add_row_last(GNT_TREE(lv->tree),
-									log,
-									gnt_tree_create_row(GNT_TREE(lv->tree), log_get_date(log)),
-									NULL);
-		}
-		g_free(read);
+	gnt_progress_bar_set_fraction(GNT_PROGRESS_BAR(lv_ex->search_bar), 0.0);
+
+	cancel = g_cancellable_new();
+	finch_log_viewer_ex_set_search_cancel(lv_ex, cancel);
+
+	purple_debug_info("gntlog", "Searching %u logs\n", length);
+
+	for (logs = lv->logs; logs != NULL; logs = g_list_next(logs)) {
+		PurpleLog *log = logs->data;
+
+		callback_data = g_new0(_finch_search_callback_data, 1);
+		callback_data->log_data = finch_log_data;
+		callback_data->log = log;
+
+		purple_log_read_async(log, NULL, G_PRIORITY_DEFAULT_IDLE, cancel,
+			finch_log_search_cb, callback_data);
 	}
 
+	g_object_unref(cancel);
 }
 
-static void destroy_cb(GntWidget *w, struct log_viewer_hash_t *ht)
+static void
+destroy_cb(GntWidget *w, log_viewer_hash_t *ht)
 {
-	FinchLogViewer *lv = syslog_viewer;
+	FinchLogViewerEx *lv_ex = syslog_viewer;
+	FinchLogViewer *lv;
+	GList *list;
 
 	if (ht != NULL) {
-		lv = g_hash_table_lookup(log_viewers, ht);
+		lv_ex = g_hash_table_lookup(log_viewers, ht);
 		g_hash_table_remove(log_viewers, ht);
 
 		g_free(ht->username);
@@ -161,61 +354,128 @@ static void destroy_cb(GntWidget *w, str
 	} else
 		syslog_viewer = NULL;
 
+	lv = lv_ex->lv;
 	purple_request_close_with_handle(lv);
 
-	g_list_foreach(lv->logs, (GFunc)purple_log_free, NULL);
+	for (list = lv->logs; list != NULL; list = g_list_next(list))
+		purple_log_free(list->data);
+
 	g_list_free(lv->logs);
 
 	g_free(lv->search);
 	g_free(lv);
 
+	if (lv_ex->read_cancel != NULL) {
+		g_cancellable_cancel(lv_ex->read_cancel);
+		g_object_unref(lv_ex->read_cancel);
+	}
+
+	if (lv_ex->search_cancel != NULL) {
+		g_cancellable_cancel(lv_ex->search_cancel);
+		g_object_unref(lv_ex->search_cancel);
+	}
+
+	if (lv_ex->list_cancel != NULL) {
+		g_cancellable_cancel(lv_ex->list_cancel);
+		g_object_unref(lv_ex->list_cancel);
+	}
+
+	g_free(lv_ex);
 	gnt_widget_destroy(w);
 }
 
-static void log_select_cb(GntWidget *w, gpointer old, gpointer new, FinchLogViewer *viewer)
+static void
+finch_log_read_cb(GObject *object, GAsyncResult *res, gpointer userdata)
 {
+	_finch_log_data *finch_log_data = userdata;
+
+	if (finch_log_data->is_window_open) {
+		PurpleLog *log = finch_log_data->log;
+		FinchLogViewerEx *lv_ex = finch_log_data->log_viewer;
+		FinchLogViewer *lv = lv_ex->lv;
+		GError *err = NULL;
+		gchar *text, *strip;
+
+		text = purple_log_read_finish(log, res, &err);
+
+		if (text == NULL) {
+			if (err->code == G_IO_ERROR_CANCELLED) {
+				finch_log_data_free(finch_log_data);
+				g_clear_error(&err);
+
+				return;
+			}
+
+			text = err->message;
+		}
+
+		if (!(lv->flags & PURPLE_LOG_READ_NO_NEWLINE)) {
+			gchar *newline = purple_strdup_withhtml(text);
+			strip = purple_markup_strip_html(newline);
+			g_free(newline);
+		} else
+			strip = purple_markup_strip_html(text);
+
+		gnt_text_view_clear(GNT_TEXT_VIEW(lv->text));
+
+		purple_signal_emit(finch_log_get_handle(), "log-displaying", lv, log);
+
+		gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(lv->text), strip,
+			GNT_TEXT_FLAG_NORMAL);
+
+		g_free(strip);
+		g_clear_error(&err);
+	}
+
+	finch_log_data_free(finch_log_data);
+}
+
+static void
+log_select_cb(GntWidget *w, gpointer old_row, gpointer new_row, FinchLogViewerEx *lv_ex)
+{
+	_finch_log_data *finch_log_data;
+	FinchLogViewer *lv = lv_ex->lv;
+	PurpleLog *log;
 	GntTree *tree = GNT_TREE(w);
-	PurpleLog *log = NULL;
-	PurpleLogReadFlags flags;
-	char *read = NULL, *strip, *newline;
+	GCancellable *cancel;
 
-	if (!viewer->search && !gnt_tree_get_parent_key(tree, new))
+	/* Make sure we don't select the month row */
+	if (lv->search == NULL && gnt_tree_get_parent_key(tree, new_row) == NULL)
 		return;
 
-	log = (PurpleLog *)new;
+	log = new_row;
 
 	if (log == NULL)
 		return;
 
 	if (log->type != PURPLE_LOG_SYSTEM) {
-		char *title;
+		gchar *title;
+
 		if (log->type == PURPLE_LOG_CHAT)
 			title = g_strdup_printf(_("Conversation in %s on %s"),
-									log->name, log_get_date(log));
+				log->name, log_get_date(log));
 		else
 			title = g_strdup_printf(_("Conversation with %s on %s"),
-									log->name, log_get_date(log));
+				log->name, log_get_date(log));
 
-		gnt_label_set_text(GNT_LABEL(viewer->label), title);
+		gnt_label_set_text(GNT_LABEL(lv->label), title);
 		g_free(title);
 	}
 
-	read = purple_log_read(log, &flags);
-	if (flags != PURPLE_LOG_READ_NO_NEWLINE) {
-		newline = purple_strdup_withhtml(read);
-		strip = purple_markup_strip_html(newline);
-		g_free(newline);
-	} else {
-		strip = purple_markup_strip_html(read);
-	}
-	viewer->flags = flags;
+	finch_log_data = g_new0(_finch_log_data, 1);
+	finch_log_data->is_window_open = TRUE;
+	finch_log_data->log_viewer = lv_ex;
+	finch_log_data->log = log;
+	finch_log_data->destroy_handler_id = g_signal_connect_swapped(lv->window,
+        "destroy", G_CALLBACK(finch_window_destroy_cb), finch_log_data);
 
-	purple_signal_emit(finch_log_get_handle(), "log-displaying", viewer, log);
+	cancel = g_cancellable_new();
+	finch_log_viewer_ex_set_read_cancel(lv_ex, cancel);
 
-	gnt_text_view_clear(GNT_TEXT_VIEW(viewer->text));
-	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(viewer->text), strip, GNT_TEXT_FLAG_NORMAL);
-	g_free(read);
-	g_free(strip);
+	purple_log_read_async(log, &lv->flags, G_PRIORITY_DEFAULT, cancel,
+		finch_log_read_cb, finch_log_data);
+
+	g_object_unref(cancel);
 }
 
 /* I want to make this smarter, but haven't come up with a cool algorithm to do so, yet.
@@ -224,106 +484,83 @@ static void log_select_cb(GntWidget *w, 
  *
  * For now, I'll just make it a flat list.
  */
-static void populate_log_tree(FinchLogViewer *lv)
+static void
+populate_log_tree(FinchLogViewerEx *lv_ex)
      /* Logs are made from trees in real life.
         This is a tree made from logs */
 {
-	const char *pmonth;
-	char *month = NULL;
-	char prev_top_month[30] = "";
-	GList *logs = lv->logs;
+	FinchLogViewer *lv = lv_ex->lv;
+	GList *logs;
+	const gchar *pmonth;
+	gchar *month = NULL, prev_top_month[30] = "";
 
-	while (logs != NULL) {
+	for (logs = lv->logs; logs != NULL; logs = g_list_next(logs)) {
 		PurpleLog *log = logs->data;
 
 		pmonth = purple_utf8_strftime(_("%B %Y"),
-		                           log->tm ? log->tm : localtime(&log->time));
+			log->tm ? log->tm : localtime(&log->time));
 
 		if (strcmp(pmonth, prev_top_month) != 0) {
 			month = g_strdup(pmonth);
+
 			/* top level */
-			gnt_tree_add_row_last(GNT_TREE(lv->tree),
-									month,
-									gnt_tree_create_row(GNT_TREE(lv->tree), month),
-									NULL);
+			gnt_tree_add_row_last(GNT_TREE(lv->tree), month,
+				gnt_tree_create_row(GNT_TREE(lv->tree), month),
+				NULL);
 			gnt_tree_set_expanded(GNT_TREE(lv->tree), month, FALSE);
 
 			strncpy(prev_top_month, month, sizeof(prev_top_month));
 		}
 
 		/* sub */
-		gnt_tree_add_row_last(GNT_TREE(lv->tree),
-								log,
-								gnt_tree_create_row(GNT_TREE(lv->tree), log_get_date(log)),
-								month);
-
-		logs = logs->next;
+		gnt_tree_add_row_last(GNT_TREE(lv->tree), log,
+			gnt_tree_create_row(GNT_TREE(lv->tree), log_get_date(log)),
+			month);
 	}
 }
 
-static FinchLogViewer *display_log_viewer(struct log_viewer_hash_t *ht, GList *logs,
-						const char *title, int log_size)
+static FinchLogViewerEx *
+display_log_viewer_nonblocking(log_viewer_hash_t *ht,
+	const gchar *title, gboolean need_log_size)
 {
+	FinchLogViewerEx *lv_ex;
 	FinchLogViewer *lv;
-	char *text;
 	GntWidget *vbox, *hbox;
-	GntWidget *size_label;
+	gchar *text;
 
-	if (logs == NULL)
-	{
-		/* No logs were found. */
-		const char *log_preferences = NULL;
-
-		if (ht == NULL) {
-			if (!purple_prefs_get_bool("/purple/logging/log_system"))
-				log_preferences = _("System events will only be logged if the \"Log all status changes to system log\" preference is enabled.");
-		} else {
-			if (ht->type == PURPLE_LOG_IM) {
-				if (!purple_prefs_get_bool("/purple/logging/log_ims"))
-					log_preferences = _("Instant messages will only be logged if the \"Log all instant messages\" preference is enabled.");
-			} else if (ht->type == PURPLE_LOG_CHAT) {
-				if (!purple_prefs_get_bool("/purple/logging/log_chats"))
-					log_preferences = _("Chats will only be logged if the \"Log all chats\" preference is enabled.");
-			}
-			g_free(ht->username);
-			g_free(ht);
-		}
-
-		purple_notify_info(NULL, title, _("No logs were found"), log_preferences);
-		return NULL;
-	}
-
+	lv_ex = g_new0(FinchLogViewerEx, 1);
 	lv = g_new0(FinchLogViewer, 1);
-	lv->logs = logs;
+	lv->logs = NULL;
+	lv_ex->lv = lv;
 
-	if (ht != NULL)
-		g_hash_table_insert(log_viewers, ht, lv);
+	if (ht != NULL) {
+		lv_ex->type = ht->type;
+		g_hash_table_insert(log_viewers, ht, lv_ex);
+	} else
+		lv_ex->type = PURPLE_LOG_SYSTEM;
 
 	/* Window ***********/
 	lv->window = gnt_vwindow_new(FALSE);
 	gnt_box_set_title(GNT_BOX(lv->window), title);
 	gnt_box_set_toplevel(GNT_BOX(lv->window), TRUE);
 	gnt_box_set_pad(GNT_BOX(lv->window), 0);
-	g_signal_connect(G_OBJECT(lv->window), "destroy", G_CALLBACK(destroy_cb), ht);
+	g_signal_connect(lv->window, "destroy", G_CALLBACK(destroy_cb), ht);
 
 	vbox = gnt_vbox_new(FALSE);
 	gnt_box_add_widget(GNT_BOX(lv->window), vbox);
 
 	/* Label ************/
-	text = g_strdup_printf("%s", title);
-	lv->label = gnt_label_new_with_format(text, GNT_TEXT_FLAG_BOLD);
-	g_free(text);
+	lv->label = gnt_label_new_with_format(title, GNT_TEXT_FLAG_BOLD);
 	gnt_box_add_widget(GNT_BOX(vbox), lv->label);
 
 	hbox = gnt_hbox_new(FALSE);
 	gnt_box_add_widget(GNT_BOX(vbox), hbox);
+
 	/* List *************/
 	lv->tree = gnt_tree_new();
 	gnt_widget_set_size(lv->tree, 30, 0);
-	populate_log_tree(lv);
-	g_signal_connect (G_OBJECT(lv->tree), "selection-changed",
-			G_CALLBACK (log_select_cb),
-			lv);
+	g_signal_connect(lv->tree, "selection-changed",
+		G_CALLBACK(log_select_cb), lv_ex);
 	gnt_box_add_widget(GNT_BOX(hbox), lv->tree);
 
 	/* Viewer ************/
@@ -333,13 +570,15 @@ static FinchLogViewer *display_log_viewe
 
 	hbox = gnt_hbox_new(FALSE);
 	gnt_box_add_widget(GNT_BOX(vbox), hbox);
+
 	/* Log size ************/
-	if (log_size) {
-		char *sz_txt = purple_str_size_to_units(log_size);
-		text = g_strdup_printf("%s %s", _("Total log size:"), sz_txt);
-		size_label = gnt_label_new(text);
-		gnt_box_add_widget(GNT_BOX(hbox), size_label);
-		g_free(sz_txt);
+	if (need_log_size) {
+		lv_ex->size = 0;
+		text = g_strdup_printf("%s %s", _("Total log size:"), _("calculating..."));
+
+		lv_ex->size_label = gnt_label_new(text);
+		gnt_box_add_widget(GNT_BOX(hbox), lv_ex->size_label);
+
 		g_free(text);
 	}
 
@@ -347,172 +586,443 @@ static FinchLogViewer *display_log_viewe
 	gnt_box_add_widget(GNT_BOX(hbox), gnt_label_new(_("Scroll/Search: ")));
 	lv->entry = gnt_entry_new("");
 	gnt_box_add_widget(GNT_BOX(hbox), lv->entry);
-	g_signal_connect(GNT_ENTRY(lv->entry), "activate", G_CALLBACK(search_cb), lv);
+	g_signal_connect(lv->entry, "activate", G_CALLBACK(search_cb), lv_ex);
 
 	gnt_text_view_attach_scroll_widget(GNT_TEXT_VIEW(lv->text), lv->entry);
 	gnt_text_view_attach_pager_widget(GNT_TEXT_VIEW(lv->text), lv->entry);
 
+	/* Progress bars **********/
+	lv_ex->list_bar = gnt_progress_bar_new();
+	lv_ex->search_bar = gnt_progress_bar_new();
+
+	gnt_progress_bar_set_fraction(GNT_PROGRESS_BAR(lv_ex->search_bar), 1.0);
+
+	gnt_box_add_widget(GNT_BOX(vbox), lv_ex->list_bar);
+	gnt_box_add_widget(GNT_BOX(vbox), lv_ex->search_bar);
+
 	gnt_widget_show(lv->window);
+//	gnt_widget_hide(lv_ex->search_bar);
 
-	return lv;
+	lv_ex->read_cancel = NULL;
+	lv_ex->search_cancel = NULL;
+	lv_ex->list_cancel = NULL;
+
+	return lv_ex;
 }
 
 static void
-our_logging_blows(PurpleLogSet *set, PurpleLogSet *setagain, GList **list)
+finch_log_done_cb(_finch_log_data *finch_log_data)
 {
-	/* The iteration happens on the first list. So we use the shorter list in front */
-	if (set->type != PURPLE_LOG_IM)
-		return;
-	*list = g_list_concat(purple_log_get_logs(PURPLE_LOG_IM, set->name, set->account), *list);
+	if (finch_log_data->is_window_open) {
+		FinchLogViewerEx *lv_ex = finch_log_data->log_viewer;
+		FinchLogViewer *lv = lv_ex->lv;
+
+//		gnt_widget_hide(lv_ex->list_bar);
+
+		if (lv->logs == NULL) {
+			/* No logs were found. */
+			const gchar *log_preferences = NULL;
+
+			if (lv_ex->type == PURPLE_LOG_SYSTEM) {
+				if (!purple_prefs_get_bool("/purple/logging/log_system"))
+					log_preferences = _("System events will only be logged if the \"Log all status changes to system log\""
+						"preference is enabled.");
+			} else if (lv_ex->type == PURPLE_LOG_IM) {
+				if (!purple_prefs_get_bool("/purple/logging/log_ims"))
+					log_preferences = _("Instant messages will only be logged if the \"Log all instant messages\""
+						"preference is enabled.");
+			} else if (lv_ex->type == PURPLE_LOG_CHAT) {
+				if (!purple_prefs_get_bool("/purple/logging/log_chats"))
+					log_preferences = _("Chats will only be logged if the \"Log all chats\" preference is enabled.");
+			}
+
+			purple_notify_info(NULL, GNT_BOX(lv->window)->title,
+				_("No logs were found"), log_preferences);
+			gnt_widget_destroy(lv->window);
+		}
+	}
+
+	finch_log_data_free(finch_log_data);
 }
 
-void finch_log_show(PurpleLogType type, const char *username, PurpleAccount *account)
+static void
+finch_log_viewer_update_list_bar(_finch_log_data *finch_log_data)
 {
-	struct log_viewer_hash_t *ht;
-	FinchLogViewer *lv = NULL;
-	const char *name = username;
-	char *title;
-	GList *logs = NULL;
-	int size = 0;
+	if (finch_log_data->is_window_open) {
+		FinchLogViewerEx *lv_ex = finch_log_data->log_viewer;
+		gdouble fraction;
 
+		fraction = (gdouble) finch_log_data->count /
+			(gdouble) finch_log_data->total;
+		gnt_progress_bar_set_fraction(GNT_PROGRESS_BAR(lv_ex->list_bar),
+			1.0 - fraction);
+	}
+}
+
+static void
+finch_log_viewer_update_search_bar(_finch_log_data *finch_log_data)
+{
+	if (finch_log_data->is_window_open) {
+		FinchLogViewerEx *lv_ex = finch_log_data->log_viewer;
+		gdouble fraction;
+
+		fraction = (gdouble) finch_log_data->count /
+			(gdouble) finch_log_data->total;
+		gnt_progress_bar_set_fraction(GNT_PROGRESS_BAR(lv_ex->search_bar),
+			1.0 - fraction);
+	}
+}
+
+static void
+finch_log_size_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_finch_log_data *finch_log_data = userdata;
+	GError *err = NULL;
+	gssize log_size;
+
+	log_size = purple_log_get_total_size_finish(res, &err);
+	finch_log_data->count--;
+
+	if (log_size < 0) {
+		if (err->code != G_IO_ERROR_CANCELLED)
+			purple_debug_error("gntlog", "Error getting total log size: %s\n", err->message);
+	} else if (finch_log_data->is_window_open) {
+		FinchLogViewerEx *lv_ex = finch_log_data->log_viewer;
+		gchar *sz_txt, *text;
+		gsize total;
+
+		total = lv_ex->size;
+		total += log_size;
+
+		lv_ex->size = total;
+
+		sz_txt = purple_str_size_to_units(total);
+		text = g_strdup_printf("%s: %s", _("Total log size"), sz_txt);
+
+		gnt_label_set_text(GNT_LABEL(lv_ex->size_label), text);
+
+		g_free(sz_txt);
+		g_free(text);
+	}
+
+	g_clear_error(&err);
+	finch_log_viewer_update_list_bar(finch_log_data);
+
+	if (finch_log_data->count < 1)
+		finch_log_done_cb(finch_log_data);
+}
+
+static void
+finch_log_list_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_finch_log_data *finch_log_data = userdata;
+	GError *err = NULL;
+	GList *list;
+
+	list = purple_log_get_logs_finish(res, &err);
+	finch_log_data->count--;
+
+	if (list == NULL) {
+		if (err != NULL && err->code != G_IO_ERROR_CANCELLED)
+			purple_debug_error("gntlog", "Error getting logs: %s\n", err->message);
+	} else if (finch_log_data->is_window_open) {
+		FinchLogViewerEx *lv_ex = finch_log_data->log_viewer;
+		FinchLogViewer *lv = lv_ex->lv;
+
+		lv->logs = g_list_concat(lv->logs, list);
+		lv->logs = g_list_sort(lv->logs, purple_log_compare);
+
+		gnt_tree_remove_all(GNT_TREE(lv->tree));
+		populate_log_tree(lv_ex);
+	}
+
+	g_clear_error(&err);
+	finch_log_viewer_update_list_bar(finch_log_data);
+
+	if (finch_log_data->count < 1)
+		finch_log_done_cb(finch_log_data);
+}
+
+static void
+finch_log_system_list_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_finch_log_data *finch_log_data = userdata;
+	GError *err = NULL;
+	GList *list;
+
+	list = purple_log_get_system_logs_finish(res, &err);
+	finch_log_data->count--;
+
+	if (list == NULL) {
+		if (err != NULL && err->code != G_IO_ERROR_CANCELLED)
+			purple_debug_error("gntlog", "Error getting system logs: %s\n", err->message);
+	} else if (finch_log_data->is_window_open) {
+		FinchLogViewerEx *lv_ex = finch_log_data->log_viewer;
+		FinchLogViewer *lv = lv_ex->lv;
+
+		lv->logs = g_list_concat(lv->logs, list);
+		lv->logs = g_list_sort(lv->logs, purple_log_compare);
+
+		gnt_tree_remove_all(GNT_TREE(lv->tree));
+		populate_log_tree(lv_ex);
+	}
+
+	g_clear_error(&err);
+	finch_log_viewer_update_list_bar(finch_log_data);
+
+	if (finch_log_data->count < 1)
+		finch_log_done_cb(finch_log_data);
+}
+
+static void
+finch_log_sets_cb(GObject *object, GAsyncResult *res, gpointer userdata)
+{
+	_finch_log_data *finch_log_data = userdata;
+	GError *err = NULL;
+	GHashTable *table;
+
+	table = purple_log_get_log_sets_finish(res, &err);
+
+	if (table == NULL) {
+		if (err != NULL && err->code != G_IO_ERROR_CANCELLED)
+			purple_debug_error("gntlog", "Error getting log sets: %s\n", err->message);
+
+		finch_log_data_free(finch_log_data);
+	} else if (finch_log_data->is_window_open) {
+		FinchLogViewerEx *lv_ex = finch_log_data->log_viewer;
+		GList *list, *n;
+		guint length;
+
+		list = g_hash_table_get_keys(table);
+		length = g_list_length(list);
+
+		if (length > 0) {
+			finch_log_data->count = finch_log_data->total = 0;
+
+			for (n = list; n != NULL; n = g_list_next(n)) {
+				PurpleLogSet *set = n->data;
+
+				if (set->type != PURPLE_LOG_IM)
+					continue;
+
+				finch_log_data->count = (finch_log_data->total += 2);
+
+				purple_log_get_logs_async(PURPLE_LOG_IM, set->name, set->account,
+					G_PRIORITY_DEFAULT, lv_ex->list_cancel, finch_log_list_cb, finch_log_data);
+				purple_log_get_total_size_async(PURPLE_LOG_IM, set->name, set->account,
+					G_PRIORITY_DEFAULT, lv_ex->list_cancel, finch_log_size_cb, finch_log_data);
+			}
+		}
+
+		g_list_free(list);
+		g_hash_table_unref(table);
+	} else
+		finch_log_data_free(finch_log_data);
+
+	g_clear_error(&err);
+}
+
+void
+finch_log_show(PurpleLogType type, const gchar *username, PurpleAccount *account)
+{
+	log_viewer_hash_t *ht;
+	_finch_log_data *finch_log_data;
+	FinchLogViewerEx *lv_ex;
+	GCancellable *cancel;
+	const gchar *name = username;
+	gchar *title;
+
 	if (type != PURPLE_LOG_IM) {
 		g_return_if_fail(account != NULL);
 		g_return_if_fail(username != NULL);
 	}
 
-	ht = g_new0(struct log_viewer_hash_t, 1);
+	ht = g_new0(log_viewer_hash_t, 1);
 
 	ht->type = type;
 	ht->username = g_strdup(username);
 	ht->account = account;
 
-	if (log_viewers == NULL) {
+	if (log_viewers == NULL)
 		log_viewers = g_hash_table_new(log_viewer_hash, log_viewer_equal);
-	} else if ((lv = g_hash_table_lookup(log_viewers, ht))) {
-		gnt_window_present(lv->window);
+	else if ((lv_ex = g_hash_table_lookup(log_viewers, ht)) != NULL) {
+		gnt_window_present(lv_ex->lv->window);
+
 		g_free(ht->username);
 		g_free(ht);
+
 		return;
 	}
 
 	if (type == PURPLE_LOG_CHAT) {
-		PurpleChat *chat;
+		PurpleChat *chat = purple_blist_find_chat(account, username);
 
-		chat = purple_blist_find_chat(account, username);
 		if (chat != NULL)
 			name = purple_chat_get_name(chat);
 
 		title = g_strdup_printf(_("Conversations in %s"), name);
 	} else {
-		PurpleBuddy *buddy;
+		if (username != NULL) {
+			PurpleBuddy *buddy = purple_find_buddy(account, username);
 
-		if (username) {
-			buddy = purple_find_buddy(account, username);
 			if (buddy != NULL)
 				name = purple_buddy_get_contact_alias(buddy);
+
 			title = g_strdup_printf(_("Conversations with %s"), name);
-		} else {
+		} else
 			title = g_strdup(_("All Conversations"));
-		}
 	}
 
-	if (username) {
-		logs = purple_log_get_logs(type, username, account);
-		size = purple_log_get_total_size(type, username, account);
+	finch_log_data = g_new0(_finch_log_data, 1);
+	finch_log_data->is_window_open = TRUE;
+
+	lv_ex = finch_log_data->log_viewer = display_log_viewer_nonblocking(ht,
+		title, TRUE);
+
+	finch_log_data->destroy_handler_id = g_signal_connect_swapped(lv_ex->lv->window,
+		"destroy", G_CALLBACK(finch_window_destroy_cb), finch_log_data);
+
+	g_free(title);
+
+	cancel = g_cancellable_new();
+	finch_log_viewer_ex_set_list_cancel(lv_ex, cancel);
+
+	if (username != NULL) {
+		finch_log_data->count = finch_log_data->total = 2;
+
+		purple_log_get_logs_async(type, username, account,
+			G_PRIORITY_DEFAULT, cancel, finch_log_list_cb, finch_log_data);
+		purple_log_get_total_size_async(type, username, account,
+			G_PRIORITY_DEFAULT, cancel, finch_log_size_cb, finch_log_data);
 	} else {
 		/* This will happen only for IMs */
-		GHashTable *table = purple_log_get_log_sets();
-		g_hash_table_foreach(table, (GHFunc)our_logging_blows, &logs);
-		g_hash_table_destroy(table);
-		logs = g_list_sort(logs, purple_log_compare);
-		size = 0;
-	}
+		finch_log_data->count = finch_log_data->total = 1;
 
-	display_log_viewer(ht, logs, title, size);
+		purple_log_get_log_sets_async(G_PRIORITY_DEFAULT, cancel,
+			finch_log_sets_cb, finch_log_data);
+	}
 
-	g_free(title);
+	g_object_unref(cancel);
 }
 
-void finch_log_show_contact(PurpleContact *contact)
+void
+finch_log_show_contact(PurpleContact *contact)
 {
-	struct log_viewer_hash_t *ht;
+	_finch_log_data *finch_log_data;
+	log_viewer_hash_t *ht;
 	PurpleBlistNode *child;
-	FinchLogViewer *lv = NULL;
-	GList *logs = NULL;
-	const char *name = NULL;
-	char *title;
-	int total_log_size = 0;
+	FinchLogViewerEx *lv_ex;
+	GCancellable *cancel;
+	const gchar *name = NULL;
+	gchar *title;
 
 	g_return_if_fail(contact != NULL);
 
-	ht = g_new0(struct log_viewer_hash_t, 1);
+	ht = g_new0(log_viewer_hash_t, 1);
 	ht->type = PURPLE_LOG_IM;
 	ht->contact = contact;
 
-	if (log_viewers == NULL) {
+	if (log_viewers == NULL)
 		log_viewers = g_hash_table_new(log_viewer_hash, log_viewer_equal);
-	} else if ((lv = g_hash_table_lookup(log_viewers, ht))) {
-		gnt_window_present(lv->window);
+	else if ((lv_ex = g_hash_table_lookup(log_viewers, ht)) != NULL) {
+		gnt_window_present(lv_ex->lv->window);
 		g_free(ht);
-		return;
-	}
 
-	for (child = purple_blist_node_get_first_child((PurpleBlistNode*)contact); child;
-			child = purple_blist_node_get_sibling_next(child)) {
-		const char *name;
-		PurpleAccount *account;
-		if (!PURPLE_BLIST_NODE_IS_BUDDY(child))
-			continue;
-
-		name = purple_buddy_get_name((PurpleBuddy *)child);
-		account = purple_buddy_get_account((PurpleBuddy *)child);
-		logs = g_list_concat(purple_log_get_logs(PURPLE_LOG_IM, name,
-						account), logs);
-		total_log_size += purple_log_get_total_size(PURPLE_LOG_IM, name, account);
+		return;
 	}
-	logs = g_list_sort(logs, purple_log_compare);
 
-	name = purple_contact_get_alias(contact);
-	if (!name)
+	if ((name = purple_contact_get_alias(contact)) == NULL)
 		name = purple_buddy_get_contact_alias(purple_contact_get_priority_buddy(contact));
 
 	/* This will happen if the contact doesn't have an alias,
 	 * and none of the contact's buddies are online.
 	 * There is probably a better way to deal with this. */
 	if (name == NULL) {
-		child = purple_blist_node_get_first_child((PurpleBlistNode*)contact);
+		child = purple_blist_node_get_first_child(PURPLE_BLIST_NODE(contact));
+
 		if (child != NULL && PURPLE_BLIST_NODE_IS_BUDDY(child))
-			name = purple_buddy_get_contact_alias((PurpleBuddy *)child);
+			name = purple_buddy_get_contact_alias(PURPLE_BUDDY(child));
+
 		if (name == NULL)
 			name = "";
 	}
 
+	finch_log_data = g_new0(_finch_log_data, 1);
+	finch_log_data->is_window_open = TRUE;
+	finch_log_data->count = finch_log_data->total = 0;
+
 	title = g_strdup_printf(_("Conversations with %s"), name);
-	display_log_viewer(ht, logs, title, total_log_size);
+	lv_ex = finch_log_data->log_viewer = display_log_viewer_nonblocking(ht, title, TRUE);
 	g_free(title);
+
+	finch_log_data->destroy_handler_id = g_signal_connect_swapped(lv_ex->lv->window,
+		"destroy", G_CALLBACK(finch_window_destroy_cb), finch_log_data);
+
+	cancel = g_cancellable_new();
+	finch_log_viewer_ex_set_list_cancel(lv_ex, cancel);
+
+	for (child = purple_blist_node_get_first_child(PURPLE_BLIST_NODE(contact)); child != NULL;
+		child = purple_blist_node_get_sibling_next(child))
+	{
+		const gchar *name;
+		PurpleAccount *account;
+
+		if (!PURPLE_BLIST_NODE_IS_BUDDY(child))
+			continue;
+
+		finch_log_data->count = (finch_log_data->total += 2);
+
+		name = purple_buddy_get_name(PURPLE_BUDDY(child));
+		account = purple_buddy_get_account(PURPLE_BUDDY(child));
+
+		purple_log_get_logs_async(PURPLE_LOG_IM, name, account,
+			G_PRIORITY_DEFAULT, cancel, finch_log_list_cb, finch_log_data);
+		purple_log_get_total_size_async(PURPLE_LOG_IM, name, account,
+			G_PRIORITY_DEFAULT, cancel, finch_log_size_cb, finch_log_data);
+	}
+
+	g_object_unref(cancel);
 }
 
-void finch_syslog_show()
+void
+finch_syslog_show(void)
 {
+	_finch_log_data *finch_log_data;
+	GCancellable *cancel;
 	GList *accounts = NULL;
-	GList *logs = NULL;
 
 	if (syslog_viewer != NULL) {
-		gnt_window_present(syslog_viewer->window);
+		gnt_window_present(syslog_viewer->lv->window);
+
 		return;
 	}
 
-	for(accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) {
+	finch_log_data = g_new0(_finch_log_data, 1);
+	finch_log_data->is_window_open = TRUE;
+	finch_log_data->count = finch_log_data->total = 0;
 
-		PurpleAccount *account = (PurpleAccount *)accounts->data;
+	syslog_viewer = finch_log_data->log_viewer = display_log_viewer_nonblocking(NULL,
+		_("System Log"), FALSE);
+
+	finch_log_data->destroy_handler_id = g_signal_connect_swapped(syslog_viewer->lv->window,
+		"destroy", G_CALLBACK(finch_window_destroy_cb), finch_log_data);
+
+	cancel = g_cancellable_new();
+	finch_log_viewer_ex_set_list_cancel(syslog_viewer, cancel);
+
+	for (accounts = purple_accounts_get_all(); accounts != NULL; accounts = g_list_next(accounts)) {
+		PurpleAccount *account = accounts->data;
+
 		if(purple_find_prpl(purple_account_get_protocol_id(account)) == NULL)
 			continue;
 
-		logs = g_list_concat(purple_log_get_system_logs(account), logs);
+		finch_log_data->count = finch_log_data->total++;
+
+		purple_log_get_system_logs_async(account,
+			G_PRIORITY_DEFAULT, cancel, finch_log_system_list_cb, finch_log_data);
 	}
-	logs = g_list_sort(logs, purple_log_compare);
 
-	syslog_viewer = display_log_viewer(NULL, logs, _("System Log"), 0);
+	g_object_unref(cancel);
 }
 
 /****************************************************************************
@@ -527,17 +1037,16 @@ finch_log_get_handle(void)
 	return &handle;
 }
 
-void finch_log_init(void)
+void
+finch_log_init(void)
 {
 	void *handle = finch_log_get_handle();
 
 	purple_signal_register(handle, "log-displaying",
-	                     purple_marshal_VOID__POINTER_POINTER,
-	                     NULL, 2,
-	                     purple_value_new(PURPLE_TYPE_BOXED,
-	                                    "FinchLogViewer *"),
-	                     purple_value_new(PURPLE_TYPE_SUBTYPE,
-	                                    PURPLE_SUBTYPE_LOG));
+		purple_marshal_VOID__POINTER_POINTER,
+		NULL, 2,
+		purple_value_new(PURPLE_TYPE_BOXED, "FinchLogViewer *"),
+		purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_LOG));
 }
 
 void
============================================================
--- finch/gntlog.h	d9f35af7e4f3cfcbebbac9d4f03f705daa2b223e
+++ finch/gntlog.h	02f892c394bbc98990f9c0bbf5f58cd0a9ace230
@@ -30,31 +30,60 @@
 #include "account.h"
 #include "gntwidget.h"
 
+/** @copydoc _FinchLogViewer */
 typedef struct _FinchLogViewer FinchLogViewer;
 
+/********************************************************
+ * DATA STRUCTURES **************************************
+ ********************************************************/
+
 /**
  * A GNT Log Viewer.  You can look at logs with it.
  */
 struct _FinchLogViewer {
-	GList *logs;                 /**< The list of logs viewed in this viewer   */
+	GList              *logs;    /**< The list of logs viewed in this viewer */
 
-	GntWidget	*window;    /**< The viewer's window                      */
-	GntWidget	*tree;      /**< The tree representing said treestore */
-	GntWidget	*text;      /**< The text to display said logs          */
-	GntWidget	*entry;     /**< The search entry, in which search terms
-	                              *   are entered                              */
-	GntWidget	*label;
-	PurpleLogReadFlags flags;      /**< The most recently used log flags         */
-	char		*search;    /**< The string currently being searched for  */
+	GntWidget          *window;  /**< The viewer's window */
+	GntWidget          *tree;    /**< The tree representing said treestore */
+	GntWidget          *text;    /**< The text to display said logs */
+	GntWidget          *entry;   /**< The search entry, in which search terms
+	                               *  are entered */
+	GntWidget          *label;   /**< The label widget with the title */
+	PurpleLogReadFlags flags;    /**< The most recently used log flags */
+	gchar              *search;  /**< The string currently being searched for */
 };
 
+G_BEGIN_DECLS
 
+/**************************************************************************/
+/** @name Log Viewer Creators                                             */
+/**************************************************************************/
 
-void finch_log_show(PurpleLogType type, const char *username, PurpleAccount *account);
+/*@{*/
+
+/**
+ * Displays the logs of a certain type for a buddy or chat on an account
+ *
+ * @param type:        The #PurpleLogType
+ * @param username:    The buddy or chat name
+ * @param account:     The account
+ */
+void finch_log_show(PurpleLogType type, const gchar *username, PurpleAccount *account);
+
+/**
+ * Displays the logs for one contact
+ *
+ * @param contact:     The #PurpleContact to display the logs of
+ */
 void finch_log_show_contact(PurpleContact *contact);
 
+/**
+ * Displays the system log
+ */
 void finch_syslog_show(void);
 
+/*@}*/
+
 /**************************************************************************/
 /** @name GNT Log Subsystem                                              */
 /**************************************************************************/


More information about the Commits mailing list