diff libgaim/log.c @ 14192:60b1bc8dbf37

[gaim-migrate @ 16863] Renamed 'core' to 'libgaim' committer: Tailor Script <tailor@pidgin.im>
author Evan Schoenberg <evan.s@dreskin.net>
date Sat, 19 Aug 2006 01:50:10 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgaim/log.c	Sat Aug 19 01:50:10 2006 +0000
@@ -0,0 +1,1732 @@
+/**
+ * @file log.c Logging API
+ * @ingroup core
+ *
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "internal.h"
+#include "account.h"
+#include "dbus-maybe.h"
+#include "debug.h"
+#include "internal.h"
+#include "log.h"
+#include "prefs.h"
+#include "util.h"
+#include "stringref.h"
+
+static GSList *loggers = NULL;
+
+static GaimLogLogger *html_logger;
+static GaimLogLogger *txt_logger;
+static GaimLogLogger *old_logger;
+
+struct _gaim_logsize_user {
+	char *name;
+	GaimAccount *account;
+};
+static GHashTable *logsize_users = NULL;
+
+static void log_get_log_sets_common(GHashTable *sets);
+
+static gsize html_logger_write(GaimLog *log, GaimMessageFlags type,
+							  const char *from, time_t time, const char *message);
+static void html_logger_finalize(GaimLog *log);
+static GList *html_logger_list(GaimLogType type, const char *sn, GaimAccount *account);
+static GList *html_logger_list_syslog(GaimAccount *account);
+static char *html_logger_read(GaimLog *log, GaimLogReadFlags *flags);
+static int html_logger_total_size(GaimLogType type, const char *name, GaimAccount *account);
+
+static GList *old_logger_list(GaimLogType type, const char *sn, GaimAccount *account);
+static int old_logger_total_size(GaimLogType type, const char *name, GaimAccount *account);
+static char * old_logger_read (GaimLog *log, GaimLogReadFlags *flags);
+static int old_logger_size (GaimLog *log);
+static void old_logger_get_log_sets(GaimLogSetCallback cb, GHashTable *sets);
+static void old_logger_finalize(GaimLog *log);
+
+static gsize txt_logger_write(GaimLog *log,
+							 GaimMessageFlags type,
+							 const char *from, time_t time, const char *message);
+static void txt_logger_finalize(GaimLog *log);
+static GList *txt_logger_list(GaimLogType type, const char *sn, GaimAccount *account);
+static GList *txt_logger_list_syslog(GaimAccount *account);
+static char *txt_logger_read(GaimLog *log, GaimLogReadFlags *flags);
+static int txt_logger_total_size(GaimLogType type, const char *name, GaimAccount *account);
+
+/**************************************************************************
+ * PUBLIC LOGGING FUNCTIONS ***********************************************
+ **************************************************************************/
+
+GaimLog *gaim_log_new(GaimLogType type, const char *name, GaimAccount *account,
+                      GaimConversation *conv, time_t time, const struct tm *tm)
+{
+	GaimLog *log;
+
+	/* IMPORTANT: Make sure to initialize all the members of GaimLog */
+	log = g_slice_new(GaimLog);
+	GAIM_DBUS_REGISTER_POINTER(log, GaimLog);
+
+	log->type = type;
+	log->name = g_strdup(gaim_normalize(account, name));
+	log->account = account;
+	log->conv = conv;
+	log->time = time;
+	log->logger = gaim_log_logger_get();
+	log->logger_data = NULL;
+
+	if (tm == NULL)
+		log->tm = NULL;
+	else
+	{
+		/* There's no need to zero this as we immediately do a direct copy. */
+		log->tm = g_slice_new(struct tm);
+
+		*(log->tm) = *tm;
+
+#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 (tmp != NULL)
+				log->tm->tm_zone = tmp;
+			else
+				/* Just shove the UTF-8 bytes in and hope... */
+				log->tm->tm_zone = g_strdup(log->tm->tm_zone);
+		}
+#endif
+	}
+
+	if (log->logger && log->logger->create)
+		log->logger->create(log);
+	return log;
+}
+
+void gaim_log_free(GaimLog *log)
+{
+	g_return_if_fail(log);
+	if (log->logger && log->logger->finalize)
+		log->logger->finalize(log);
+	g_free(log->name);
+
+	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);
+	}
+
+	GAIM_DBUS_UNREGISTER_POINTER(log);
+	g_slice_free(GaimLog, log);
+}
+
+void gaim_log_write(GaimLog *log, GaimMessageFlags type,
+		    const char *from, time_t time, const char *message)
+{
+	struct _gaim_logsize_user *lu;
+	gsize written, total = 0;
+	gpointer ptrsize;
+
+	g_return_if_fail(log);
+	g_return_if_fail(log->logger);
+	g_return_if_fail(log->logger->write);
+
+	written = (log->logger->write)(log, type, from, time, message);
+
+	lu = g_new(struct _gaim_logsize_user, 1);
+
+	lu->name = g_strdup(gaim_normalize(log->account, log->name));
+	lu->account = log->account;
+
+	if(g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize)) {
+		total = GPOINTER_TO_INT(ptrsize);
+		total += written;
+		g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(total));
+	} else {
+		g_free(lu->name);
+		g_free(lu);
+	}
+
+}
+
+char *gaim_log_read(GaimLog *log, GaimLogReadFlags *flags)
+{
+	GaimLogReadFlags mflags;
+	g_return_val_if_fail(log && log->logger, NULL);
+	if (log->logger->read) {
+		char *ret = (log->logger->read)(log, flags ? flags : &mflags);
+		gaim_str_strip_char(ret, '\r');
+		return ret;
+	}
+	return g_strdup(_("<b><font color=\"red\">The logger has no read function</font></b>"));
+}
+
+int gaim_log_get_size(GaimLog *log)
+{
+	g_return_val_if_fail(log && log->logger, 0);
+
+	if (log->logger->size)
+		return log->logger->size(log);
+	return 0;
+}
+
+static guint _gaim_logsize_user_hash(struct _gaim_logsize_user *lu)
+{
+	return g_str_hash(lu->name);
+}
+
+static guint _gaim_logsize_user_equal(struct _gaim_logsize_user *lu1,
+		struct _gaim_logsize_user *lu2)
+{
+	return (lu1->account == lu2->account && (!strcmp(lu1->name, lu2->name)));
+}
+
+static void _gaim_logsize_user_free_key(struct _gaim_logsize_user *lu)
+{
+	g_free(lu->name);
+	g_free(lu);
+}
+
+int gaim_log_get_total_size(GaimLogType type, const char *name, GaimAccount *account)
+{
+	gpointer ptrsize;
+	int size = 0;
+	GSList *n;
+	struct _gaim_logsize_user *lu;
+
+	lu = g_new(struct _gaim_logsize_user, 1);
+	lu->name = g_strdup(gaim_normalize(account, name));
+	lu->account = account;
+
+	if(g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize)) {
+		size = GPOINTER_TO_INT(ptrsize);
+		g_free(lu->name);
+		g_free(lu);
+	} else {
+		for (n = loggers; n; n = n->next) {
+			GaimLogLogger *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) {
+					GaimLog *log = (GaimLog*)(logs->data);
+					this_size += gaim_log_get_size(log);
+					gaim_log_free(log);
+					logs = g_list_delete_link(logs, logs);
+				}
+
+				size += this_size;
+			}
+		}
+
+		g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(size));
+	}
+	return size;
+}
+
+char *
+gaim_log_get_log_dir(GaimLogType type, const char *name, GaimAccount *account)
+{
+	GaimPlugin *prpl;
+	GaimPluginProtocolInfo *prpl_info;
+	const char *prpl_name;
+	char *acct_name;
+	const char *target;
+	char *dir;
+
+	prpl = gaim_find_prpl(gaim_account_get_protocol_id(account));
+	if (!prpl)
+		return NULL;
+	prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
+	prpl_name = prpl_info->list_icon(account, NULL);
+
+	acct_name = g_strdup(gaim_escape_filename(gaim_normalize(account,
+				gaim_account_get_username(account))));
+
+	if (type == GAIM_LOG_CHAT) {
+		char *temp = g_strdup_printf("%s.chat", gaim_normalize(account, name));
+		target = gaim_escape_filename(temp);
+		g_free(temp);
+	} else if(type == GAIM_LOG_SYSTEM) {
+		target = ".system";
+	} else {
+		target = gaim_escape_filename(gaim_normalize(account, name));
+	}
+
+	dir = g_build_filename(gaim_user_dir(), "logs", prpl_name, acct_name, target, NULL);
+
+	g_free(acct_name);
+
+	return dir;
+}
+
+/****************************************************************************
+ * LOGGER FUNCTIONS *********************************************************
+ ****************************************************************************/
+
+static GaimLogLogger *current_logger = NULL;
+
+static void logger_pref_cb(const char *name, GaimPrefType type,
+						   gconstpointer value, gpointer data)
+{
+	GaimLogLogger *logger;
+	GSList *l = loggers;
+	while (l) {
+		logger = l->data;
+		if (!strcmp(logger->id, value)) {
+			gaim_log_logger_set(logger);
+			return;
+		}
+		l = l->next;
+	}
+	gaim_log_logger_set(txt_logger);
+}
+
+
+GaimLogLogger *gaim_log_logger_new(const char *id, const char *name, int functions, ...)
+{
+#if 0
+				void(*create)(GaimLog *),
+				gsize(*write)(GaimLog *, GaimMessageFlags, const char *, time_t, const char *),
+				void(*finalize)(GaimLog *),
+				GList*(*list)(GaimLogType type, const char*, GaimAccount*),
+				char*(*read)(GaimLog*, GaimLogReadFlags*),
+				int(*size)(GaimLog*),
+				int(*total_size)(GaimLogType type, const char *name, GaimAccount *account),
+				GList*(*list_syslog)(GaimAccount *account),
+				void(*get_log_sets)(GaimLogSetCallback cb, GHashTable *sets))
+{
+#endif
+	GaimLogLogger *logger;
+	va_list args;
+
+	g_return_val_if_fail(id != NULL, NULL);
+	g_return_val_if_fail(name != NULL, NULL);
+	g_return_val_if_fail(functions >= 1, NULL);
+
+	logger = g_new0(GaimLogLogger, 1);
+	logger->id = g_strdup(id);
+	logger->name = g_strdup(name);
+
+	va_start(args, functions);
+
+	if (functions >= 1)
+		logger->create = va_arg(args, void *);
+	if (functions >= 2)
+		logger->write = va_arg(args, void *);
+	if (functions >= 3)
+		logger->finalize = va_arg(args, void *);
+	if (functions >= 4)
+		logger->list = va_arg(args, void *);
+	if (functions >= 5)
+		logger->read = va_arg(args, void *);
+	if (functions >= 6)
+		logger->size = va_arg(args, void *);
+	if (functions >= 7)
+		logger->total_size = va_arg(args, void *);
+	if (functions >= 8)
+		logger->list_syslog = va_arg(args, void *);
+	if (functions >= 9)
+		logger->get_log_sets = va_arg(args, void *);
+
+	if (functions > 9)
+		gaim_debug_info("log", "Dropping new functions for logger: %s (%s)\n", name, id);
+
+	va_end(args);
+
+	return logger;
+}
+
+void gaim_log_logger_free(GaimLogLogger *logger)
+{
+	g_free(logger->name);
+	g_free(logger->id);
+	g_free(logger);
+}
+
+void gaim_log_logger_add (GaimLogLogger *logger)
+{
+	g_return_if_fail(logger);
+	if (g_slist_find(loggers, logger))
+		return;
+	loggers = g_slist_append(loggers, logger);
+}
+
+void gaim_log_logger_remove (GaimLogLogger *logger)
+{
+	g_return_if_fail(logger);
+	loggers = g_slist_remove(loggers, logger);
+}
+
+void gaim_log_logger_set (GaimLogLogger *logger)
+{
+	g_return_if_fail(logger);
+	current_logger = logger;
+}
+
+GaimLogLogger *gaim_log_logger_get()
+{
+	return current_logger;
+}
+
+GList *gaim_log_logger_get_options(void)
+{
+	GSList *n;
+	GList *list = NULL;
+	GaimLogLogger *data;
+
+	for (n = loggers; n; n = n->next) {
+		data = n->data;
+		if (!data->write)
+			continue;
+		list = g_list_append(list, data->name);
+		list = g_list_append(list, data->id);
+	}
+
+	return list;
+}
+
+gint gaim_log_compare(gconstpointer y, gconstpointer z)
+{
+	const GaimLog *a = y;
+	const GaimLog *b = z;
+
+	return b->time - a->time;
+}
+
+GList *gaim_log_get_logs(GaimLogType type, const char *name, GaimAccount *account)
+{
+	GList *logs = NULL;
+	GSList *n;
+	for (n = loggers; n; n = n->next) {
+		GaimLogLogger *logger = n->data;
+		if (!logger->list)
+			continue;
+		logs = g_list_concat(logger->list(type, name, account), logs);
+	}
+
+	return g_list_sort(logs, gaim_log_compare);
+}
+
+gint gaim_log_set_compare(gconstpointer y, gconstpointer z)
+{
+	const GaimLogSet *a = y;
+	const GaimLogSet *b = z;
+	gint ret = 0;
+
+	/* This logic seems weird at first...
+	 * If either account is NULL, we pretend the accounts are
+	 * equal. This allows us to detect duplicates that will
+	 * exist if one logger knows the account and another
+	 * doesn't. */
+	if (a->account != NULL && b->account != NULL) {
+		ret = strcmp(gaim_account_get_username(a->account), gaim_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;
+}
+
+static guint
+log_set_hash(gconstpointer key)
+{
+	const GaimLogSet *set = key;
+
+	/* The account isn't hashed because we need GaimLogSets with NULL accounts
+	 * to be found when we search by a GaimLogSet that has a non-NULL account
+	 * but the same type and name. */
+	return g_int_hash((gint *)&set->type) + g_str_hash(set->name);
+}
+
+static gboolean
+log_set_equal(gconstpointer a, gconstpointer b)
+{
+	/* I realize that the choices made for GList and GHashTable
+	 * make sense for those data types, but I wish the comparison
+	 * functions were compatible. */
+	return !gaim_log_set_compare(a, b);
+}
+
+static void
+log_add_log_set_to_hash(GHashTable *sets, GaimLogSet *set)
+{
+	GaimLogSet *existing_set = g_hash_table_lookup(sets, set);
+
+	if (existing_set == NULL)
+		g_hash_table_insert(sets, set, set);
+	else if (existing_set->account == NULL && set->account != NULL)
+		g_hash_table_replace(sets, set, set);
+	else
+		gaim_log_set_free(set);
+}
+
+GHashTable *gaim_log_get_log_sets(void)
+{
+	GSList *n;
+	GHashTable *sets = g_hash_table_new_full(log_set_hash, log_set_equal,
+											 (GDestroyNotify)gaim_log_set_free, NULL);
+
+	/* Get the log sets from all the loggers. */
+	for (n = loggers; n; n = n->next) {
+		GaimLogLogger *logger = n->data;
+
+		if (!logger->get_log_sets)
+			continue;
+
+		logger->get_log_sets(log_add_log_set_to_hash, sets);
+	}
+
+	log_get_log_sets_common(sets);
+
+	/* Return the GHashTable of unique GaimLogSets. */
+	return sets;
+}
+
+void gaim_log_set_free(GaimLogSet *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(GaimLogSet, set);
+}
+
+GList *gaim_log_get_system_logs(GaimAccount *account)
+{
+	GList *logs = NULL;
+	GSList *n;
+	for (n = loggers; n; n = n->next) {
+		GaimLogLogger *logger = n->data;
+		if (!logger->list_syslog)
+			continue;
+		logs = g_list_concat(logger->list_syslog(account), logs);
+	}
+
+	return g_list_sort(logs, gaim_log_compare);
+}
+
+/****************************************************************************
+ * LOG SUBSYSTEM ************************************************************
+ ****************************************************************************/
+
+void *
+gaim_log_get_handle(void)
+{
+	static int handle;
+
+	return &handle;
+}
+
+void gaim_log_init(void)
+{
+	void *handle = gaim_log_get_handle();
+
+	gaim_prefs_add_none("/core/logging");
+	gaim_prefs_add_bool("/core/logging/log_ims", FALSE);
+	gaim_prefs_add_bool("/core/logging/log_chats", FALSE);
+	gaim_prefs_add_bool("/core/logging/log_system", FALSE);
+
+	gaim_prefs_add_string("/core/logging/format", "txt");
+
+	html_logger = gaim_log_logger_new("html", _("HTML"), 8,
+									  NULL,
+									  html_logger_write,
+									  html_logger_finalize,
+									  html_logger_list,
+									  html_logger_read,
+									  gaim_log_common_sizer,
+									  html_logger_total_size,
+									  html_logger_list_syslog);
+	gaim_log_logger_add(html_logger);
+
+	txt_logger = gaim_log_logger_new("txt", _("Plain text"), 8,
+									 NULL,
+									 txt_logger_write,
+									 txt_logger_finalize,
+									 txt_logger_list,
+									 txt_logger_read,
+									 gaim_log_common_sizer,
+									 txt_logger_total_size,
+									 txt_logger_list_syslog);
+	gaim_log_logger_add(txt_logger);
+
+	old_logger = gaim_log_logger_new("old", _("Old Gaim"), 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);
+	gaim_log_logger_add(old_logger);
+
+	gaim_signal_register(handle, "log-timestamp",
+#if SIZEOF_TIME_T == 4
+	                     gaim_marshal_POINTER__POINTER_INT,
+#elif SIZEOF_TIME_T == 8
+			     gaim_marshal_POINTER__POINTER_INT64,
+#else
+#error Unknown size of time_t
+#endif
+	                     gaim_value_new(GAIM_TYPE_POINTER), 2,
+	                     gaim_value_new(GAIM_TYPE_SUBTYPE,
+	                                    GAIM_SUBTYPE_LOG),
+#if SIZEOF_TIME_T == 4
+	                     gaim_value_new(GAIM_TYPE_INT));
+#elif SIZEOF_TIME_T == 8
+	                     gaim_value_new(GAIM_TYPE_INT64));
+#else
+# error Unknown size of time_t
+#endif
+
+	gaim_prefs_connect_callback(NULL, "/core/logging/format",
+							    logger_pref_cb, NULL);
+	gaim_prefs_trigger_callback("/core/logging/format");
+
+	logsize_users = g_hash_table_new_full((GHashFunc)_gaim_logsize_user_hash,
+			(GEqualFunc)_gaim_logsize_user_equal,
+			(GDestroyNotify)_gaim_logsize_user_free_key, NULL);
+}
+
+void
+gaim_log_uninit(void)
+{
+	gaim_signals_unregister_by_instance(gaim_log_get_handle());
+}
+
+/****************************************************************************
+ * LOGGERS ******************************************************************
+ ****************************************************************************/
+
+static char *log_get_timestamp(GaimLog *log, time_t when)
+{
+	char *date;
+	struct tm tm;
+
+	date = gaim_signal_emit_return_1(gaim_log_get_handle(),
+	                          "log-timestamp",
+	                          log, when);
+	if (date != NULL)
+		return date;
+
+	tm = *(localtime(&when));
+	if (log->type == GAIM_LOG_SYSTEM || time(NULL) > when + 20*60)
+		return g_strdup(gaim_date_format_long(&tm));
+	else
+		return g_strdup(gaim_time_format(&tm));
+}
+
+void gaim_log_common_writer(GaimLog *log, const char *ext)
+{
+	GaimLogCommonLoggerData *data = log->logger_data;
+
+	if (data == NULL)
+	{
+		/* This log is new */
+		char *dir;
+		struct tm *tm;
+		const char *tz;
+		const char *date;
+		char *filename;
+		char *path;
+
+		dir = gaim_log_get_log_dir(log->type, log->name, log->account);
+		if (dir == NULL)
+			return;
+
+		gaim_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
+
+		tm = localtime(&log->time);
+		tz = gaim_escape_filename(gaim_utf8_strftime("%Z", tm));
+		date = gaim_utf8_strftime("%Y-%m-%d.%H%M%S%z", tm);
+
+		filename = g_strdup_printf("%s%s%s", date, tz, ext ? ext : "");
+
+		path = g_build_filename(dir, filename, NULL);
+		g_free(dir);
+		g_free(filename);
+
+		log->logger_data = data = g_slice_new0(GaimLogCommonLoggerData);
+
+		data->file = g_fopen(path, "a");
+		if (data->file == NULL)
+		{
+			gaim_debug(GAIM_DEBUG_ERROR, "log",
+					"Could not create log file %s\n", path);
+
+			if (log->conv != NULL)
+				gaim_conversation_write(log->conv, NULL, _("Logging of this conversation failed."),
+										GAIM_MESSAGE_ERROR, time(NULL));
+
+			g_free(path);
+			return;
+		}
+		g_free(path);
+	}
+}
+
+GList *gaim_log_common_lister(GaimLogType type, const char *name, GaimAccount *account, const char *ext, GaimLogLogger *logger)
+{
+	GDir *dir;
+	GList *list = NULL;
+	const char *filename;
+	char *path;
+
+	if(!account)
+		return NULL;
+
+	path = gaim_log_get_log_dir(type, name, account);
+	if (path == NULL)
+		return NULL;
+
+	if (!(dir = g_dir_open(path, 0, NULL)))
+	{
+		g_free(path);
+		return NULL;
+	}
+
+	while ((filename = g_dir_read_name(dir)))
+	{
+		if (gaim_str_has_suffix(filename, ext) &&
+		    strlen(filename) >= (17 + strlen(ext)))
+		{
+			GaimLog *log;
+			GaimLogCommonLoggerData *data;
+			struct tm tm;
+#if defined (HAVE_TM_GMTOFF) && defined (HAVE_STRUCT_TM_TM_ZONE)
+			long tz_off;
+			const char *rest;
+			time_t stamp = gaim_str_to_time(gaim_unescape_filename(filename), FALSE, &tm, &tz_off, &rest);
+			char *end;
+
+			/* As zero is a valid offset, GAIM_NO_TZ_OFF means no offset was
+			 * provided. See util.h. Yes, it's kinda ugly. */
+			if (tz_off != GAIM_NO_TZ_OFF)
+				tm.tm_gmtoff = tz_off - tm.tm_gmtoff;
+
+			if (rest == NULL || (end = strchr(rest, '.')) == NULL || strchr(rest, ' ') != NULL)
+			{
+				log = gaim_log_new(type, name, account, NULL, stamp, NULL);
+			}
+			else
+			{
+				char *tmp = g_strndup(rest, end - rest);
+				tm.tm_zone = tmp;
+				log = gaim_log_new(type, name, account, NULL, stamp, &tm);
+				g_free(tmp);
+			}
+#else
+			time_t stamp = gaim_str_to_time(filename, FALSE, &tm, NULL, NULL);
+
+			log = gaim_log_new(type, name, account, NULL, stamp, &tm);
+#endif
+
+			log->logger = logger;
+			log->logger_data = data = g_slice_new0(GaimLogCommonLoggerData);
+
+			data->path = g_build_filename(path, filename, NULL);
+			list = g_list_prepend(list, log);
+		}
+	}
+	g_dir_close(dir);
+	g_free(path);
+	return list;
+}
+
+int gaim_log_common_total_sizer(GaimLogType type, const char *name, GaimAccount *account, const char *ext)
+{
+	GDir *dir;
+	int size = 0;
+	const char *filename;
+	char *path;
+
+	if(!account)
+		return 0;
+
+	path = gaim_log_get_log_dir(type, name, account);
+	if (path == NULL)
+		return 0;
+
+	if (!(dir = g_dir_open(path, 0, NULL)))
+	{
+		g_free(path);
+		return 0;
+	}
+
+	while ((filename = g_dir_read_name(dir)))
+	{
+		if (gaim_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))
+			{
+				gaim_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);
+	return size;
+}
+
+int gaim_log_common_sizer(GaimLog *log)
+{
+	struct stat st;
+	GaimLogCommonLoggerData *data = log->logger_data;
+
+	if (!data->path || g_stat(data->path, &st))
+		st.st_size = 0;
+
+	return st.st_size;
+}
+
+/* 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)
+{
+	gchar *log_path = g_build_filename(gaim_user_dir(), "logs", NULL);
+	GDir *log_dir = g_dir_open(log_path, 0, NULL);
+	const gchar *protocol;
+
+	if (log_dir == NULL) {
+		g_free(log_path);
+		return;
+	}
+
+	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;
+
+		if ((protocol_dir = g_dir_open(protocol_path, 0, NULL)) == NULL) {
+			g_free(protocol_path);
+			continue;
+		}
+
+		/* Using g_strdup() to cover the one-in-a-million chance that a
+		 * prpl's list_icon function uses gaim_unescape_filename(). */
+		protocol_unescaped = g_strdup(gaim_unescape_filename(protocol));
+
+		/* Find all the accounts for protocol. */
+		for (account_iter = gaim_accounts_get_all() ; account_iter != NULL ; account_iter = account_iter->next) {
+			GaimPlugin *prpl;
+			GaimPluginProtocolInfo *prpl_info;
+
+			prpl = gaim_find_prpl(gaim_account_get_protocol_id((GaimAccount *)account_iter->data));
+			if (!prpl)
+				continue;
+			prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
+
+			if (!strcmp(protocol_unescaped, prpl_info->list_icon((GaimAccount *)account_iter->data, NULL)))
+				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;
+			GaimAccount *account = NULL;
+			gchar *name;
+
+			if ((username_dir = g_dir_open(username_path, 0, NULL)) == NULL) {
+				g_free(username_path);
+				continue;
+			}
+
+			/* Find the account for username in the list of accounts for protocol. */
+			username_unescaped = gaim_unescape_filename(username);
+			for (account_iter = g_list_first(accounts) ; account_iter != NULL ; account_iter = account_iter->next) {
+				if (!strcmp(((GaimAccount *)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;
+				GaimLogSet *set;
+
+				/* IMPORTANT: Always initialize all members of GaimLogSet */
+				set = g_slice_new(GaimLogSet);
+
+				/* Unescape the filename. */
+				name = g_strdup(gaim_unescape_filename(name));
+
+				/* Get the (possibly new) length of name. */
+				len = strlen(name);
+
+				set->type = GAIM_LOG_IM;
+				set->name = name;
+				set->account = account;
+				/* set->buddy is always set below */
+				set->normalized_name = g_strdup(gaim_normalize(account, name));
+
+				/* Chat for .chat or .system at the end of the name to determine the type. */
+				if (len > 7) {
+					gchar *tmp = &name[len - 7];
+					if (!strcmp(tmp, ".system")) {
+						set->type = GAIM_LOG_SYSTEM;
+						*tmp = '\0';
+					}
+				}
+				if (len > 5) {
+					gchar *tmp = &name[len - 5];
+					if (!strcmp(tmp, ".chat")) {
+						set->type = GAIM_LOG_CHAT;
+						*tmp = '\0';
+					}
+				}
+
+				/* Determine if this (account, name) combination exists as a buddy. */
+				if (account != NULL)
+					set->buddy = (gaim_find_buddy(account, name) != NULL);
+				else
+					set->buddy = FALSE;
+
+				log_add_log_set_to_hash(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);
+}
+
+#if 0 /* Maybe some other time. */
+/****************
+ ** XML LOGGER **
+ ****************/
+
+static const char *str_from_msg_type (GaimMessageFlags type)
+{
+
+		return "";
+
+}
+
+static void xml_logger_write(GaimLog *log,
+			     GaimMessageFlags type,
+			     const char *from, time_t time, const char *message)
+{
+	char *xhtml = NULL;
+
+	if (!log->logger_data) {
+		/* 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.
+		 */
+		struct tm *tm;
+		const char *tz;
+		const char *date;
+		char *dir = gaim_log_get_log_dir(log->type, log->name, log->account);
+		char *name;
+		char *filename;
+
+		if (dir == NULL)
+			return;
+
+		tm = localtime(&log->time);
+		tz = gaim_escape_filename(gaim_utf8_strftime("%Z", tm);
+		date = gaim_utf8_strftime("%Y-%m-%d.%H%M%S%z", tm);
+
+		name = g_strdup_printf("%s%s%s", date, tz, ext ? ext : "");
+
+		gaim_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
+
+		filename = g_build_filename(dir, name, NULL);
+		g_free(dir);
+		g_free(name);
+
+		log->logger_data = g_fopen(filename, "a");
+		if (!log->logger_data) {
+			gaim_debug(GAIM_DEBUG_ERROR, "log", "Could not create log file %s\n", filename);
+			g_free(filename);
+			return;
+		}
+		g_free(filename);
+		fprintf(log->logger_data, "<?xml version='1.0' encoding='UTF-8' ?>\n"
+			"<?xml-stylesheet href='file:///usr/src/web/htdocs/log-stylesheet.xsl' type='text/xml' ?>\n");
+
+		date = gaim_utf8_strftime("%Y-%m-%d %H:%M:%S", localtime(&log->time));
+		fprintf(log->logger_data, "<conversation time='%s' screenname='%s' protocol='%s'>\n",
+			date, log->name, prpl);
+	}
+
+	/* if we can't write to the file, give up before we hurt ourselves */
+	if(!data->file)
+		return;
+
+	date = log_get_timestamp(log, time);
+
+	gaim_markup_html_to_xhtml(message, &xhtml, NULL);
+	if (from)
+		fprintf(log->logger_data, "<message %s %s from='%s' time='%s'>%s</message>\n",
+			str_from_msg_type(type),
+			type & GAIM_MESSAGE_SEND ? "direction='sent'" :
+			type & GAIM_MESSAGE_RECV ? "direction='received'" : "",
+			from, date, xhtml);
+	else
+		fprintf(log->logger_data, "<message %s %s time='%s'>%s</message>\n",
+			str_from_msg_type(type),
+			type & GAIM_MESSAGE_SEND ? "direction='sent'" :
+			type & GAIM_MESSAGE_RECV ? "direction='received'" : "",
+			date, xhtml):
+	fflush(log->logger_data);
+	g_free(date);
+	g_free(xhtml);
+}
+
+ static void xml_logger_finalize(GaimLog *log)
+{
+	if (log->logger_data) {
+		fprintf(log->logger_data, "</conversation>\n");
+		fclose(log->logger_data);
+		log->logger_data = NULL;
+	}
+}
+
+static GList *xml_logger_list(GaimLogType type, const char *sn, GaimAccount *account)
+{
+	return gaim_log_common_lister(type, sn, account, ".xml", &xml_logger);
+}
+
+static GaimLogLogger xml_logger =  {
+	N_("XML"), "xml",
+	NULL,
+	xml_logger_write,
+	xml_logger_finalize,
+	xml_logger_list,
+	NULL,
+	NULL,
+	NULL
+};
+#endif
+
+/****************************
+ ** HTML LOGGER *************
+ ****************************/
+
+static gsize html_logger_write(GaimLog *log, GaimMessageFlags type,
+							  const char *from, time_t time, const char *message)
+{
+	char *msg_fixed;
+	char *date;
+	char *header;
+	GaimPlugin *plugin = gaim_find_prpl(gaim_account_get_protocol_id(log->account));
+	GaimLogCommonLoggerData *data = log->logger_data;
+	gsize written = 0;
+
+	if(!data) {
+		const char *prpl =
+			GAIM_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL);
+		const char *date;
+		gaim_log_common_writer(log, ".html");
+
+		data = log->logger_data;
+
+		/* if we can't write to the file, give up before we hurt ourselves */
+		if(!data->file)
+			return 0;
+
+		date = gaim_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 == GAIM_LOG_SYSTEM)
+			header = g_strdup_printf("System log for account %s (%s) connected at %s",
+					gaim_account_get_username(log->account), prpl, date);
+		else
+			header = g_strdup_printf("Conversation with %s at %s on %s (%s)",
+					log->name, date, gaim_account_get_username(log->account), prpl);
+
+		written += fprintf(data->file, "%s", header);
+		written += fprintf(data->file, "</title></head><body>");
+		written += fprintf(data->file, "<h3>%s</h3>\n", header);
+		g_free(header);
+	}
+
+	/* if we can't write to the file, give up before we hurt ourselves */
+	if(!data->file)
+		return 0;
+
+	gaim_markup_html_to_xhtml(message, &msg_fixed, NULL);
+	date = log_get_timestamp(log, time);
+
+	if(log->type == GAIM_LOG_SYSTEM){
+		written += fprintf(data->file, "---- %s @ %s ----<br/>\n", msg_fixed, date);
+	} else {
+		if (type & GAIM_MESSAGE_SYSTEM)
+			written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s</b><br/>\n", date, msg_fixed);
+		else if (type & GAIM_MESSAGE_ERROR)
+			written += fprintf(data->file, "<font color=\"#FF0000\"><font size=\"2\">(%s)</font><b> %s</b></font><br/>\n", date, msg_fixed);
+		else if (type & GAIM_MESSAGE_WHISPER)
+			written += fprintf(data->file, "<font color=\"#6C2585\"><font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n",
+					date, from, msg_fixed);
+		else if (type & GAIM_MESSAGE_AUTO_RESP) {
+			if (type & GAIM_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, from, msg_fixed);
+			else if (type & GAIM_MESSAGE_RECV)
+				written += fprintf(data->file, _("<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s &lt;AUTO-REPLY&gt;:</b></font> %s<br/>\n"), date, from, msg_fixed);
+		} else if (type & GAIM_MESSAGE_RECV) {
+			if(gaim_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, from, msg_fixed);
+			else
+				written += fprintf(data->file, "<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
+						date, from, msg_fixed);
+		} else if (type & GAIM_MESSAGE_SEND) {
+			if(gaim_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, from, msg_fixed);
+			else
+				written += fprintf(data->file, "<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
+						date, from, msg_fixed);
+		} else {
+			gaim_debug_error("log", "Unhandled message type.");
+			written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n",
+						date, from, msg_fixed);
+		}
+	}
+	g_free(date);
+	g_free(msg_fixed);
+	fflush(data->file);
+
+	return written;
+}
+
+static void html_logger_finalize(GaimLog *log)
+{
+	GaimLogCommonLoggerData *data = log->logger_data;
+	if (data) {
+		if(data->file) {
+			fprintf(data->file, "</body></html>\n");
+			fclose(data->file);
+		}
+		g_free(data->path);
+
+		g_slice_free(GaimLogCommonLoggerData, data);
+	}
+}
+
+static GList *html_logger_list(GaimLogType type, const char *sn, GaimAccount *account)
+{
+	return gaim_log_common_lister(type, sn, account, ".html", html_logger);
+}
+
+static GList *html_logger_list_syslog(GaimAccount *account)
+{
+	return gaim_log_common_lister(GAIM_LOG_SYSTEM, ".system", account, ".html", html_logger);
+}
+
+static char *html_logger_read(GaimLog *log, GaimLogReadFlags *flags)
+{
+	char *read;
+	GaimLogCommonLoggerData *data = log->logger_data;
+	*flags = GAIM_LOG_READ_NO_NEWLINE;
+	if (!data || !data->path)
+		return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>"));
+	if (g_file_get_contents(data->path, &read, NULL, NULL)) {
+		char *minus_header = strchr(read, '\n');
+
+		if (!minus_header)
+			return read;
+
+		minus_header = g_strdup(minus_header + 1);
+		g_free(read);
+
+		return minus_header;
+	}
+	return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data->path);
+}
+
+static int html_logger_total_size(GaimLogType type, const char *name, GaimAccount *account)
+{
+	return gaim_log_common_total_sizer(type, name, account, ".html");
+}
+
+
+/****************************
+ ** PLAIN TEXT LOGGER *******
+ ****************************/
+
+static gsize txt_logger_write(GaimLog *log,
+							 GaimMessageFlags type,
+							 const char *from, time_t time, const char *message)
+{
+	char *date;
+	GaimPlugin *plugin = gaim_find_prpl(gaim_account_get_protocol_id(log->account));
+	GaimLogCommonLoggerData *data = log->logger_data;
+	char *stripped = NULL;
+
+	gsize 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 =
+			GAIM_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL);
+		gaim_log_common_writer(log, ".txt");
+
+		data = log->logger_data;
+
+		/* if we can't write to the file, give up before we hurt ourselves */
+		if(!data->file)
+			return 0;
+
+		if (log->type == GAIM_LOG_SYSTEM)
+			written += fprintf(data->file, "System log for account %s (%s) connected at %s\n",
+				gaim_account_get_username(log->account), prpl,
+				gaim_date_format_full(localtime(&log->time)));
+		else
+			written += fprintf(data->file, "Conversation with %s at %s on %s (%s)\n",
+				log->name, gaim_date_format_full(localtime(&log->time)),
+				gaim_account_get_username(log->account), prpl);
+	}
+
+	/* if we can't write to the file, give up before we hurt ourselves */
+	if(!data->file)
+		return 0;
+
+	stripped = gaim_markup_strip_html(message);
+	date = log_get_timestamp(log, time);
+
+	if(log->type == GAIM_LOG_SYSTEM){
+		written += fprintf(data->file, "---- %s @ %s ----\n", stripped, date);
+	} else {
+		if (type & GAIM_MESSAGE_SEND ||
+			type & GAIM_MESSAGE_RECV) {
+			if (type & GAIM_MESSAGE_AUTO_RESP) {
+				written += fprintf(data->file, _("(%s) %s <AUTO-REPLY>: %s\n"), date,
+						from, stripped);
+			} else {
+				if(gaim_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);
+			}
+		} else if (type & GAIM_MESSAGE_SYSTEM)
+			written += fprintf(data->file, "(%s) %s\n", date, stripped);
+		else if (type & GAIM_MESSAGE_NO_LOG) {
+			/* This shouldn't happen */
+			g_free(stripped);
+			return written;
+		} else if (type & GAIM_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);
+	}
+	g_free(date);
+	g_free(stripped);
+	fflush(data->file);
+
+	return written;
+}
+
+static void txt_logger_finalize(GaimLog *log)
+{
+	GaimLogCommonLoggerData *data = log->logger_data;
+	if (data) {
+		if(data->file)
+			fclose(data->file);
+		g_free(data->path);
+
+		g_slice_free(GaimLogCommonLoggerData, data);
+	}
+}
+
+static GList *txt_logger_list(GaimLogType type, const char *sn, GaimAccount *account)
+{
+	return gaim_log_common_lister(type, sn, account, ".txt", txt_logger);
+}
+
+static GList *txt_logger_list_syslog(GaimAccount *account)
+{
+	return gaim_log_common_lister(GAIM_LOG_SYSTEM, ".system", account, ".txt", txt_logger);
+}
+
+static char *txt_logger_read(GaimLog *log, GaimLogReadFlags *flags)
+{
+	char *read, *minus_header, *minus_header2;
+	GaimLogCommonLoggerData *data = log->logger_data;
+	*flags = 0;
+	if (!data || !data->path)
+		return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>"));
+	if (g_file_get_contents(data->path, &read, NULL, NULL)) {
+		minus_header = strchr(read, '\n');
+		if (!minus_header)
+			minus_header = g_strdup(read);
+		else
+			minus_header = g_strdup(minus_header + 1);
+		g_free(read);
+		minus_header2 = g_markup_escape_text(minus_header, -1);
+		g_free(minus_header);
+		read = gaim_markup_linkify(minus_header2);
+		g_free(minus_header2);
+		return read;
+	}
+	return g_strdup_printf(_("<font color=\"red\"><b>Could not read file: %s</b></font>"), data->path);
+}
+
+static int txt_logger_total_size(GaimLogType type, const char *name, GaimAccount *account)
+{
+	return gaim_log_common_total_sizer(type, name, account, ".txt");
+}
+
+
+/****************
+ * OLD LOGGER ***
+ ****************/
+
+/* The old logger doesn't write logs, only reads them.  This is to include
+ * old logs in the log viewer transparently.
+ */
+
+struct old_logger_data {
+	GaimStringref *pathref;
+	int offset;
+	int length;
+};
+
+static GList *old_logger_list(GaimLogType type, const char *sn, GaimAccount *account)
+{
+	char *logfile = g_strdup_printf("%s.log", gaim_normalize(account, sn));
+	char *pathstr = g_build_filename(gaim_user_dir(), "logs", logfile, NULL);
+	GaimStringref *pathref = gaim_stringref_new(pathstr);
+	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;
+	char *newlog;
+	int logfound = 0;
+	int lastoff = 0;
+	int newlen;
+	time_t lasttime = 0;
+
+	GaimLog *log = NULL;
+	GList *list = NULL;
+
+	g_free(logfile);
+
+	if (g_stat(gaim_stringref_value(pathref), &st))
+	{
+		gaim_stringref_unref(pathref);
+		g_free(pathstr);
+		return NULL;
+	}
+	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)
+		{
+			gaim_debug_warning("log", "Index \"%s\" exists, but is older than the log.\n", pathstr);
+		}
+		else
+		{
+			/* The index file exists and is at least as new as the log, so open it. */
+			if (!(index = g_fopen(pathstr, "rb")))
+			{
+				gaim_debug_error("log", "Failed to open index file \"%s\" for reading: %s\n",
+				                 pathstr, strerror(errno));
+
+				/* Fall through so that we'll parse the log file. */
+			}
+			else
+			{
+				gaim_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)
+					{
+						log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, -1, NULL);
+						log->logger = old_logger;
+						log->time = (time_t)idx_time;
+
+						/* IMPORTANT: Always set all members of struct old_logger_data */
+						data = g_slice_new(struct old_logger_data);
+
+						data->pathref = gaim_stringref_ref(pathref);
+						data->offset = lastoff;
+						data->length = newlen;
+
+						log->logger_data = data;
+						list = g_list_prepend(list, log);
+					}
+				}
+				fclose(index);
+
+				return list;
+			}
+		}
+	}
+
+	if (!(file = g_fopen(gaim_stringref_value(pathref), "rb"))) {
+		gaim_debug_error("log", "Failed to open log file \"%s\" for reading: %s\n",
+		                   gaim_stringref_value(pathref), strerror(errno));
+		gaim_stringref_unref(pathref);
+		g_free(pathstr);
+		return NULL;
+	}
+
+	index_tmp = g_strdup_printf("%s.XXXXXX", pathstr);
+	if ((index_fd = g_mkstemp(index_tmp)) == -1) {
+		gaim_debug_error("log", "Failed to open index temp file: %s\n",
+		                 strerror(errno));
+		g_free(pathstr);
+		g_free(index_tmp);
+		index = NULL;
+	} else {
+		if ((index = fdopen(index_fd, "wb")) == NULL)
+		{
+			gaim_debug_error("log", "Failed to fdopen() index temp file: %s\n",
+			                 strerror(errno));
+			close(index_fd);
+			if (index_tmp != NULL)
+			{
+				g_unlink(index_tmp);
+				g_free(index_tmp);
+			}
+			g_free(pathstr);
+		}
+	}
+
+	while (fgets(buf, BUF_LONG, file)) {
+		if ((newlog = strstr(buf, "---- New C"))) {
+			int length;
+			int offset;
+			char convostart[32];
+			char *temp = strchr(buf, '@');
+
+			if (temp == NULL || strlen(temp) < 2)
+				continue;
+
+			temp++;
+			length = strcspn(temp, "-");
+			if (length > 31) length = 31;
+
+			offset = ftell(file);
+
+			if (logfound) {
+				newlen = offset - lastoff - length;
+				if(strstr(buf, "----</H3><BR>")) {
+					newlen -=
+						sizeof("<HR><BR><H3 Align=Center> ---- New Conversation @ ") +
+						sizeof("----</H3><BR>") - 2;
+				} else {
+					newlen -=
+						sizeof("---- New Conversation @ ") + sizeof("----") - 2;
+				}
+
+				if(strchr(buf, '\r'))
+					newlen--;
+
+				if (newlen != 0) {
+					log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, -1, NULL);
+					log->logger = old_logger;
+					log->time = lasttime;
+
+					/* IMPORTANT: Always set all members of struct old_logger_data */
+					data = g_slice_new(struct old_logger_data);
+
+					data->pathref = gaim_stringref_ref(pathref);
+					data->offset = lastoff;
+					data->length = newlen;
+
+					log->logger_data = data;
+					list = g_list_prepend(list, log);
+
+					/* 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);
+				}
+			}
+
+			logfound = 1;
+			lastoff = offset;
+
+			g_snprintf(convostart, length, "%s", temp);
+			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 (strcmp(month, "Jan") == 0) {
+				tm.tm_mon= 0;
+			} else if (strcmp(month, "Feb") == 0) {
+				tm.tm_mon = 1;
+			} else if (strcmp(month, "Mar") == 0) {
+				tm.tm_mon = 2;
+			} else if (strcmp(month, "Apr") == 0) {
+				tm.tm_mon = 3;
+			} else if (strcmp(month, "May") == 0) {
+				tm.tm_mon = 4;
+			} else if (strcmp(month, "Jun") == 0) {
+				tm.tm_mon = 5;
+			} else if (strcmp(month, "Jul") == 0) {
+				tm.tm_mon = 6;
+			} else if (strcmp(month, "Aug") == 0) {
+				tm.tm_mon = 7;
+			} else if (strcmp(month, "Sep") == 0) {
+				tm.tm_mon = 8;
+			} else if (strcmp(month, "Oct") == 0) {
+				tm.tm_mon = 9;
+			} else if (strcmp(month, "Nov") == 0) {
+				tm.tm_mon = 10;
+			} else if (strcmp(month, "Dec") == 0) {
+				tm.tm_mon = 11;
+			}
+			tm.tm_year -= 1900;
+			lasttime = mktime(&tm);
+		}
+	}
+
+	if (logfound) {
+		if ((newlen = ftell(file) - lastoff) != 0) {
+			log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, -1, NULL);
+			log->logger = old_logger;
+			log->time = lasttime;
+
+			/* IMPORTANT: Always set all members of struct old_logger_data */
+			data = g_slice_new(struct old_logger_data);
+
+			data->pathref = gaim_stringref_ref(pathref);
+			data->offset = lastoff;
+			data->length = newlen;
+
+			log->logger_data = data;
+			list = g_list_prepend(list, log);
+
+			/* 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);
+		}
+	}
+
+	gaim_stringref_unref(pathref);
+	fclose(file);
+	if (index != NULL)
+	{
+		fclose(index);
+
+		if (index_tmp == NULL)
+		{
+			g_free(pathstr);
+			g_return_val_if_reached(list);
+		}
+
+		if (g_rename(index_tmp, pathstr))
+		{
+			gaim_debug_warning("log", "Failed to rename index temp file \"%s\" to \"%s\": %s\n",
+			                   index_tmp, pathstr, strerror(errno));
+			g_unlink(index_tmp);
+			g_free(index_tmp);
+		}
+		else
+			gaim_debug_info("log", "Built index: %s\n", pathstr);
+
+		g_free(pathstr);
+	}
+	return list;
+}
+
+static int old_logger_total_size(GaimLogType type, const char *name, GaimAccount *account)
+{
+	char *logfile = g_strdup_printf("%s.log", gaim_normalize(account, name));
+	char *pathstr = g_build_filename(gaim_user_dir(), "logs", logfile, NULL);
+	int size;
+	struct stat st;
+
+	if (g_stat(pathstr, &st))
+		size = 0;
+	else
+		size = st.st_size;
+
+	g_free(logfile);
+	g_free(pathstr);
+
+	return size;
+}
+
+static char * old_logger_read (GaimLog *log, GaimLogReadFlags *flags)
+{
+	struct old_logger_data *data = log->logger_data;
+	FILE *file = g_fopen(gaim_stringref_value(data->pathref), "rb");
+	char *tmp, *read = g_malloc(data->length + 1);
+	fseek(file, data->offset, SEEK_SET);
+	fread(read, data->length, 1, file);
+	fclose(file);
+	read[data->length] = '\0';
+	*flags = 0;
+	if(strstr(read, "<BR>"))
+		*flags |= GAIM_LOG_READ_NO_NEWLINE;
+	else {
+		tmp = g_markup_escape_text(read, -1);
+		g_free(read);
+		read = gaim_markup_linkify(tmp);
+		g_free(tmp);
+	}
+	return read;
+}
+
+static int old_logger_size (GaimLog *log)
+{
+	struct old_logger_data *data = log->logger_data;
+	return data ? data->length : 0;
+}
+
+static void old_logger_get_log_sets(GaimLogSetCallback cb, GHashTable *sets)
+{
+	char *log_path = g_build_filename(gaim_user_dir(), "logs", NULL);
+	GDir *log_dir = g_dir_open(log_path, 0, NULL);
+	gchar *name;
+	GaimBlistNode *gnode, *cnode, *bnode;
+
+	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;
+		GaimLogSet *set;
+		gboolean found = FALSE;
+
+		/* Unescape the filename. */
+		name = g_strdup(gaim_unescape_filename(name));
+
+		/* Get the (possibly new) length of name. */
+		len = strlen(name);
+
+		if (len < 5) {
+			g_free(name);
+			continue;
+		}
+
+		/* Make sure we're dealing with a log file. */
+		ext = &name[len - 4];
+		if (strcmp(ext, ".log")) {
+			g_free(name);
+			continue;
+		}
+
+		/* IMPORTANT: Always set all members of GaimLogSet */
+		set = g_slice_new(GaimLogSet);
+
+		/* Chat for .chat at the end of the name to determine the type. */
+		*ext = '\0';
+		set->type = GAIM_LOG_IM;
+		if (len > 9) {
+			char *tmp = &name[len - 9];
+			if (!strcmp(tmp, ".chat")) {
+				set->type = GAIM_LOG_CHAT;
+				*tmp = '\0';
+			}
+		}
+
+		set->name = set->normalized_name = name;
+
+		/* Search the buddy list to find the account and to determine if this is a buddy. */
+		for (gnode = gaim_get_blist()->root; !found && gnode != NULL; gnode = gnode->next)
+		{
+			if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
+				continue;
+
+			for (cnode = gnode->child; !found && cnode != NULL; cnode = cnode->next)
+			{
+				if (!GAIM_BLIST_NODE_IS_CONTACT(cnode))
+					continue;
+
+				for (bnode = cnode->child; !found && bnode != NULL; bnode = bnode->next)
+				{
+					GaimBuddy *buddy = (GaimBuddy *)bnode;
+
+					if (!strcmp(buddy->name, name)) {
+						set->account = buddy->account;
+						set->buddy = TRUE;
+						found = TRUE;
+					}
+				}
+			}
+		}
+
+		if (!found)
+		{
+			set->account = NULL;
+			set->buddy = FALSE;
+		}
+
+		cb(sets, set);
+	}
+	g_dir_close(log_dir);
+}
+
+static void old_logger_finalize(GaimLog *log)
+{
+	struct old_logger_data *data = log->logger_data;
+	gaim_stringref_unref(data->pathref);
+	g_slice_free(struct old_logger_data, data);
+}