Mercurial > pidgin
changeset 11459:b8f6f1fd30c0
[gaim-migrate @ 13698]
I'm committing this so it doesn't get lost in a tragic hard drive accident or something. It's nowhere near finished. The code is pretty bad and I haven't touched it (except for API updates) in probably six or seven months. It was some of the first code I ever wrote for Gaim, so no making fun of me. If you have a desire to work on it, be my guest. :)
committer: Tailor Script <tailor@pidgin.im>
author | Richard Laager <rlaager@wiktel.com> |
---|---|
date | Tue, 06 Sep 2005 04:42:27 +0000 |
parents | 4db38b374d3f |
children | d68ca756e983 |
files | plugins/log_reader.c |
diffstat | 1 files changed, 2005 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/log_reader.c Tue Sep 06 04:42:27 2005 +0000 @@ -0,0 +1,2005 @@ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <stdio.h> + +#ifndef GAIM_PLUGINS +# define GAIM_PLUGINS +#endif + +#include "internal.h" + +#include "debug.h" +#include "log.h" +#include "plugin.h" +#include "pluginpref.h" +#include "prefs.h" +#include "stringref.h" +#include "util.h" +#include "version.h" +#include "xmlnode.h" + +/* This must be the last Gaim header included. */ +#ifdef _WIN32 +#include "win32dep.h" +#endif + +/* Where is the Windows partition mounted? */ +#define LOG_READER_WINDOWS_MOUNT_POINT "/mnt/windows" + +enum name_guesses { + NAME_GUESS_UNKNOWN, + NAME_GUESS_ME, + NAME_GUESS_THEM +}; + + +/***************************************************************************** + * Adium Logger * + *****************************************************************************/ + +/* The adium logger doesn't write logs, only reads them. This is to include + * Adium logs in the log viewer transparently. + */ + +static GaimLogLogger adium_logger; + +enum adium_log_type { + ADIUM_HTML, + ADIUM_TEXT, +}; + +struct adium_logger_data { + char *path; + enum adium_log_type type; +}; + +static GList *adium_logger_list(GaimLogType type, const char *sn, GaimAccount *account) +{ + GList *list = NULL; + const char *logdir; + GaimPlugin *plugin; + GaimPluginProtocolInfo *prpl_info; + char *prpl_name; + char *temp; + char *path; + GDir *dir; + + g_return_val_if_fail(sn != NULL, list); + g_return_val_if_fail(account != NULL, list); + + logdir = gaim_prefs_get_string("/plugins/core/log_reader/adium/log_directory"); + + /* By clearing the log directory path, this logger can be (effectively) disabled. */ + if (!*logdir) + return list; + + plugin = gaim_find_prpl(gaim_account_get_protocol_id(account)); + if (!plugin) + return NULL; + + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(plugin); + if (!prpl_info->list_icon) + return NULL; + + prpl_name = g_ascii_strup(prpl_info->list_icon(account, NULL), -1); + + temp = g_strdup_printf("%s.%s", prpl_name, account->username); + path = g_build_filename(logdir, temp, sn, NULL); + g_free(temp); + + dir = g_dir_open(path, 0, NULL); + if (dir) { + const gchar *file; + + while ((file = g_dir_read_name(dir))) { + if (!g_str_has_prefix(file, sn)) + continue; + if (g_str_has_suffix(file, ".html")) { + struct tm tm; + const char *date = file; + + date += strlen(sn) + 2; + if (sscanf(date, "%u|%u|%u", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday) != 3) { + + gaim_debug(GAIM_DEBUG_ERROR, "Adium log parse", + "Filename timestamp parsing error\n"); + } else { + char *filename = g_build_filename(path, file, NULL); + FILE *handle = fopen(filename, "rb"); + char *contents; + char *contents2; + struct adium_logger_data *data; + GaimLog *log; + + if (!handle) { + g_free(filename); + continue; + } + + /* XXX: This is really inflexible. */ + contents = g_malloc(57); + fread(contents, 56, 1, handle); + fclose(handle); + contents[56] = '\0'; + + /* XXX: This is fairly inflexible. */ + contents2 = contents; + while (*contents2 && *contents2 != '>') + contents2++; + if (*contents2) + contents2++; + while (*contents2 && *contents2 != '>') + contents2++; + if (*contents2) + contents2++; + + if (sscanf(contents2, "%u.%u.%u", + &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 3) { + + gaim_debug(GAIM_DEBUG_ERROR, "Adium log parse", + "Contents timestamp parsing error\n"); + g_free(contents); + g_free(filename); + continue; + } + g_free(contents); + + data = g_new0(struct adium_logger_data, 1); + data->path = filename; + data->type = ADIUM_HTML; + + tm.tm_year -= 1900; + tm.tm_mon -= 1; + + log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, mktime(&tm)); + log->logger = &adium_logger; + log->logger_data = data; + + list = g_list_append(list, log); + } + } else if (g_str_has_suffix(file, ".adiumLog")) { + struct tm tm; + const char *date = file; + + date += strlen(sn) + 2; + if (sscanf(date, "%u|%u|%u", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday) != 3) { + + gaim_debug(GAIM_DEBUG_ERROR, "Adium log parse", + "Filename timestamp parsing error\n"); + } else { + char *filename = g_build_filename(path, file, NULL); + FILE *handle = fopen(filename, "rb"); + char *contents; + char *contents2; + struct adium_logger_data *data; + GaimLog *log; + + if (!handle) { + g_free(filename); + continue; + } + + /* XXX: This is really inflexible. */ + contents = g_malloc(14); + fread(contents, 13, 1, handle); + fclose(handle); + contents[13] = '\0'; + + contents2 = contents; + while (*contents2 && *contents2 != '(') + contents2++; + if (*contents2) + contents2++; + + if (sscanf(contents2, "%u.%u.%u", + &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 3) { + + gaim_debug(GAIM_DEBUG_ERROR, "Adium log parse", + "Contents timestamp parsing error\n"); + g_free(contents); + g_free(filename); + continue; + } + + g_free(contents); + + tm.tm_year -= 1900; + tm.tm_mon -= 1; + + data = g_new0(struct adium_logger_data, 1); + data->path = filename; + data->type = ADIUM_TEXT; + + log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, mktime(&tm)); + log->logger = &adium_logger; + log->logger_data = data; + + list = g_list_append(list, log); + } + } + } + g_dir_close(dir); + } + + g_free(prpl_name); + g_free(path); + + return list; +} + +static char *adium_logger_read (GaimLog *log, GaimLogReadFlags *flags) +{ + struct adium_logger_data *data; + GError *error = NULL; + gchar *read = NULL; + gsize length; + + g_return_val_if_fail(log != NULL, g_strdup("")); + + data = log->logger_data; + + g_return_val_if_fail(data->path != NULL, g_strdup("")); + + gaim_debug(GAIM_DEBUG_INFO, "Adium log read", + "Reading %s\n", data->path); + if (!g_file_get_contents(data->path, &read, &length, &error)) { + gaim_debug(GAIM_DEBUG_ERROR, "Adium log read", + "Error reading log\n"); + if (error) + g_error_free(error); + return g_strdup(""); + } + + if (data->type != ADIUM_HTML) { + char *escaped = g_markup_escape_text(read, -1); + g_free(read); + read = escaped; + } + +#ifdef WIN32 + /* This problem only seems to show up on Windows. + * The BOM is displaying as a space at the beginning of the log. + */ + if (g_str_has_prefix(read, "\xef\xbb\xbf")) + { + /* FIXME: This feels so wrong... */ + char *temp = g_strdup(&(read[3])); + g_free(read); + read = temp; + } +#endif + + /* TODO: Apply formatting. + * Replace the above hack with something better, since we'll + * be looping over the entire log file contents anyway. + */ + + return read; +} + +static int adium_logger_size (GaimLog *log) +{ + struct adium_logger_data *data; + char *text; + size_t size; + + g_return_val_if_fail(log != NULL, 0); + + data = log->logger_data; + + if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) { + struct stat st; + + if (!data->path || stat(data->path, &st)) + st.st_size = 0; + + return st.st_size; + } + + text = adium_logger_read(log, NULL); + size = strlen(text); + g_free(text); + + return size; +} + +static void adium_logger_finalize(GaimLog *log) +{ + struct adium_logger_data *data; + + g_return_if_fail(log != NULL); + + data = log->logger_data; + + g_free(data->path); +} + +static GaimLogLogger adium_logger = { + N_("Adium Log Reader"), "adium", + NULL, NULL, + adium_logger_finalize, + adium_logger_list, + adium_logger_read, + adium_logger_size, + NULL, + NULL, + NULL +}; + + +/***************************************************************************** + * Fire Logger * + *****************************************************************************/ + +/* The fire logger doesn't write logs, only reads them. This is to include + * Fire logs in the log viewer transparently. + */ + +static GaimLogLogger fire_logger; + +struct fire_logger_data { +}; + +static GList *fire_logger_list(GaimLogType type, const char *sn, GaimAccount *account) +{ + // TODO: Do something here. + return NULL; +} + +static char * fire_logger_read (GaimLog *log, GaimLogReadFlags *flags) +{ + struct fire_logger_data *data; + + g_return_val_if_fail(log != NULL, g_strdup("")); + + data = log->logger_data; + + // TODO: Do something here. + return g_strdup(""); +} + +static int fire_logger_size (GaimLog *log) +{ + g_return_val_if_fail(log != NULL, 0); + + if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) + return 0; + + // TODO: Do something here. + return 0; +} + +static void fire_logger_finalize(GaimLog *log) +{ + g_return_if_fail(log != NULL); + + // TODO: Do something here. +} + +static GaimLogLogger fire_logger = { + N_("Fire Log Reader"), "fire", + NULL, NULL, + fire_logger_finalize, + fire_logger_list, + fire_logger_read, + fire_logger_size, + NULL, + NULL, + NULL +}; + + +/***************************************************************************** + * Messenger Plus! Logger * + *****************************************************************************/ + +/* The messenger_plus logger doesn't write logs, only reads them. This is to include + * Messenger Plus! logs in the log viewer transparently. + */ + +static GaimLogLogger messenger_plus_logger; + +struct messenger_plus_logger_data { +}; + +static GList *messenger_plus_logger_list(GaimLogType type, const char *sn, GaimAccount *account) +{ + // TODO: Do something here. + return NULL; +} + +static char * messenger_plus_logger_read (GaimLog *log, GaimLogReadFlags *flags) +{ + struct messenger_plus_logger_data *data = log->logger_data; + + g_return_val_if_fail(log != NULL, g_strdup("")); + + data = log->logger_data; + + // TODO: Do something here. + return g_strdup(""); +} + +static int messenger_plus_logger_size (GaimLog *log) +{ + g_return_val_if_fail(log != NULL, 0); + + if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) + return 0; + + // TODO: Do something here. + return 0; +} + +static void messenger_plus_logger_finalize(GaimLog *log) +{ + g_return_if_fail(log != NULL); + + // TODO: Do something here. +} + +static GaimLogLogger messenger_plus_logger = { + N_("Messenger Plus Log Reader"), "messenger_plus", + NULL, NULL, + messenger_plus_logger_finalize, + messenger_plus_logger_list, + messenger_plus_logger_read, + messenger_plus_logger_size, + NULL, + NULL, + NULL +}; + + +/***************************************************************************** + * MSN Messenger Logger * + *****************************************************************************/ + +/* The msn logger doesn't write logs, only reads them. This is to include + * MSN Messenger message histories in the log viewer transparently. + */ + +static GaimLogLogger msn_logger; + +struct msn_logger_data { + xmlnode *root; + xmlnode *message; + const char *session_id; + int last_log; + GString *text; +}; + +static time_t msn_logger_parse_timestamp(xmlnode *message) +{ + const char *date; + const char *time; + struct tm tm; + char am_pm; + + g_return_val_if_fail(message != NULL, (time_t)0); + + date = xmlnode_get_attrib(message, "Date"); + if (!(date && *date)) { + gaim_debug(GAIM_DEBUG_ERROR, "MSN log timestamp parse", + "Attribute missing: %s\n", "Date"); + return (time_t)0; + } + + time = xmlnode_get_attrib(message, "Time"); + if (!(time && *time)) { + gaim_debug(GAIM_DEBUG_ERROR, "MSN log timestamp parse", + "Attribute missing: %s\n", "Time"); + return (time_t)0; + } + + if (sscanf(date, "%u/%u/%u", &tm.tm_mon, &tm.tm_mday, &tm.tm_year) != 3) + gaim_debug(GAIM_DEBUG_ERROR, "MSN log timestamp parse", + "%s parsing error\n", "Date"); + + if (sscanf(time, "%u:%u:%u %c", &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &am_pm) != 4) + gaim_debug(GAIM_DEBUG_ERROR, "MSN log timestamp parse", + "%s parsing error\n", "Time"); + + tm.tm_year -= 1900; + tm.tm_mon -= 1; + if (am_pm == 'P') { + tm.tm_hour += 12; + } else if (tm.tm_hour == 12) { + /* 12 AM = 00 hr */ + tm.tm_hour = 0; + } + /* Let the C library deal with daylight savings time. */ + tm.tm_isdst = -1; + + return mktime(&tm); +} + + +static GList *msn_logger_list(GaimLogType type, const char *sn, GaimAccount *account) +{ + GList *list = NULL; + char *username; + GaimBuddy *buddy; + const char *logdir; + const char *savedfilename = NULL; + char *logfile; + char *path; + GError *error = NULL; + gchar *contents = NULL; + gsize length; + xmlnode *root; + xmlnode *message; + const char *old_session_id = ""; + struct msn_logger_data *data = NULL; + + g_return_val_if_fail(sn != NULL, list); + g_return_val_if_fail(account != NULL, list); + + if (strcmp(account->protocol_id, "prpl-msn")) + return list; + + logdir = gaim_prefs_get_string("/plugins/core/log_reader/msn/log_directory"); + + /* By clearing the log directory path, this logger can be (effectively) disabled. */ + if (!*logdir) + return list; + + buddy = gaim_find_buddy(account, sn); + + if ((username = g_strdup(gaim_account_get_string( + account, "log_reader_msn_log_folder", NULL)))) { + /* As a special case, we allow the null string to kill the parsing + * straight away. This would allow the user to deal with the case + * when two account have the same username at different domains and + * only one has logs stored. + */ + if (!*username) { + g_free(username); + return list; + } + } else { + username = g_strdup(gaim_normalize(account, account->username)); + } + + if (buddy) + savedfilename = gaim_blist_node_get_string(&buddy->node, "log_reader_msn_log_filename"); + + if (savedfilename) { + /* As a special case, we allow the null string to kill the parsing + * straight away. This would allow the user to deal with the case + * when two buddies have the same username at different domains and + * only one has logs stored. + */ + if (!*savedfilename) { + g_free(username); + return list; + } + + logfile = g_strdup(savedfilename); + } else { + logfile = g_strdup_printf("%s.xml", gaim_normalize(account, sn)); + } + + path = g_build_filename(logdir, username, "History", logfile, NULL); + + if (!g_file_test(path, G_FILE_TEST_EXISTS)) { + gboolean found = FALSE; + char *at_sign; + GDir *dir; + + g_free(path); + + if (savedfilename) { + /* We had a saved filename, but it doesn't exist. + * Returning now is the right course of action because we don't + * want to detect another file incorrectly. + */ + g_free(username); + g_free(logfile); + return list; + } + + /* Perhaps we're using a new version of MSN with the weird numbered folders. + * I don't know how the numbers are calculated, so I'm going to attempt to + * find logs by pattern matching... + */ + + at_sign = g_strrstr(username, "@"); + if (at_sign) + *at_sign = '\0'; + + dir = g_dir_open(logdir, 0, NULL); + if (dir) { + const gchar *name; + + while ((name = g_dir_read_name(dir))) { + const char *c = name; + + if (!g_str_has_prefix(c, username)) + continue; + + c += strlen(username); + while (*c) { + if (!g_ascii_isdigit(*c)) + break; + + c++; + } + + path = g_build_filename(logdir, name, NULL); + /* The !c makes sure we got to the end of the while loop above. */ + if (!*c && g_file_test(path, G_FILE_TEST_IS_DIR)) { + char *history_path = g_build_filename( + path, "History", NULL); + if (g_file_test(history_path, G_FILE_TEST_IS_DIR)) { + gaim_account_set_string(account, + "log_reader_msn_log_folder", name); + g_free(path); + path = history_path; + found = TRUE; + break; + } + g_free(path); + g_free(history_path); + } + else + g_free(path); + } + g_dir_close(dir); + } + g_free(username); + + if (!found) { + g_free(logfile); + return list; + } + + /* If we've reached this point, we've found a History folder. */ + + username = g_strdup(gaim_normalize(account, sn)); + at_sign = g_strrstr(username, "@"); + if (at_sign) + *at_sign = '\0'; + + found = FALSE; + dir = g_dir_open(path, 0, NULL); + if (dir) { + const gchar *name; + + while ((name = g_dir_read_name(dir))) { + const char *c = name; + + if (!g_str_has_prefix(c, username)) + continue; + + c += strlen(username); + while (*c) { + if (!g_ascii_isdigit(*c)) + break; + + c++; + } + + path = g_build_filename(path, name, NULL); + if (!strcmp(c, ".xml") && + g_file_test(path, G_FILE_TEST_EXISTS)) { + found = TRUE; + g_free(logfile); + logfile = g_strdup(name); + break; + } + else + g_free(path); + } + g_dir_close(dir); + } + g_free(username); + + if (!found) { + g_free(logfile); + return list; + } + } else { + g_free(username); + g_free(logfile); + logfile = NULL; /* No sense saving the obvious buddy@domain.com. */ + } + + gaim_debug(GAIM_DEBUG_INFO, "MSN log read", + "Reading %s\n", path); + if (!g_file_get_contents(path, &contents, &length, &error)) { + g_free(path); + gaim_debug(GAIM_DEBUG_ERROR, "MSN log read", + "Error reading log\n"); + if (error) + g_error_free(error); + return list; + } + g_free(path); + + /* Reading the file was successful... + * Save its name if it involves the crazy numbers. The idea here is that you could + * then tweak the blist.xml file by hand if need be. This would be the case if two + * buddies have the same username at different domains. One set of logs would get + * detected for both buddies. + * + * I can't think of how buddy would be NULL. + */ + if (buddy && logfile) { + gaim_blist_node_set_string(&buddy->node, "log_reader_msn_log_filename", logfile); + g_free(logfile); + } + + root = xmlnode_from_str(contents, length); + g_free(contents); + if (!root) + return list; + + for (message = xmlnode_get_child(root, "Message"); message; + message = xmlnode_get_next_twin(message)) { + const char *session_id; + + session_id = xmlnode_get_attrib(message, "SessionID"); + if (!session_id) { + gaim_debug(GAIM_DEBUG_ERROR, "MSN log parse", + "Error parsing message: %s\n", "SessionID missing"); + continue; + } + + if (strcmp(session_id, old_session_id)) { + /* + * The session ID differs from the last message. + * Thus, this is the start of a new conversation. + */ + GaimLog *log; + + data = g_new0(struct msn_logger_data, 1); + data->root = root; + data->message = message; + data->session_id = session_id; + data->text = NULL; + data->last_log = FALSE; + + log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, msn_logger_parse_timestamp(message)); + log->logger = &msn_logger; + log->logger_data = data; + + list = g_list_append(list, log); + } + old_session_id = session_id; + } + + if (data) + data->last_log = TRUE; + + return list; +} + +static char * msn_logger_read (GaimLog *log, GaimLogReadFlags *flags) +{ + struct msn_logger_data *data; + GString *text = NULL; + xmlnode *message; + + g_return_val_if_fail(log != NULL, g_strdup("")); + + data = log->logger_data; + + if (data->text) { + /* The GTK code which displays the logs g_free()s whatever is + * returned from this function. Thus, we can't reuse the str + * part of the GString. The only solution is to free it and + * start over. + */ + g_string_free(data->text, FALSE); + } + + text = g_string_new(""); + + if (!data->root || !data->message || !data->session_id) { + /* Something isn't allocated correctly. */ + gaim_debug(GAIM_DEBUG_ERROR, "MSN log parse", + "Error parsing message: %s\n", "Internal variables inconsistent"); + data->text = text; + + return text->str; + } + + for (message = data->message; message; + message = xmlnode_get_next_twin(message)) { + + const char *new_session_id; + xmlnode *text_node; + const char *from_name = NULL; + const char *to_name = NULL; + xmlnode *from; + xmlnode *to; + enum name_guesses name_guessed = NAME_GUESS_UNKNOWN; + const char *their_name; + time_t time_unix; + struct tm *tm_new; + char *timestamp; + const char *style; + + new_session_id = xmlnode_get_attrib(message, "SessionID"); + + /* If this triggers, something is wrong with the XML. */ + if (!new_session_id) { + gaim_debug(GAIM_DEBUG_ERROR, "MSN log parse", + "Error parsing message: %s\n", "New SessionID missing"); + break; + } + + if (strcmp(new_session_id, data->session_id)) { + /* The session ID differs from the first message. + * Thus, this is the start of a new conversation. + */ + break; + } + + text_node = xmlnode_get_child(message, "Text"); + if (!text_node) + continue; + + from = xmlnode_get_child(message, "From"); + if (from) { + xmlnode *user = xmlnode_get_child(from, "User"); + + if (user) { + from_name = xmlnode_get_attrib(user, "FriendlyName"); + + /* This saves a check later. */ + if (!*from_name) + from_name = NULL; + } + } + + to = xmlnode_get_child(message, "To"); + if (to) { + xmlnode *user = xmlnode_get_child(to, "User"); + if (user) { + to_name = xmlnode_get_attrib(user, "FriendlyName"); + + /* This saves a check later. */ + if (!*to_name) + to_name = NULL; + } + } + + their_name = from_name; + if (from_name && gaim_prefs_get_bool("/plugins/core/log_reader/use_name_heuristics")) { + const char *friendly_name = gaim_connection_get_display_name(log->account->gc); + int friendly_name_length = strlen(friendly_name); + int alias_length = strlen(log->account->alias); + GaimBuddy *buddy = gaim_find_buddy(log->account, log->name); + gboolean from_name_matches; + gboolean to_name_matches; + + if (buddy->alias) + their_name = buddy->alias; + + /* Try to guess which user is me. + * The first step is to determine if either of the names matches either my + * friendly name or alias. For this test, "match" is defined as: + * ^(friendly_name|alias)([^a-zA-Z0-9].*)?$ + */ + from_name_matches = ((g_str_has_prefix( + from_name, friendly_name) && + !isalnum(*(from_name + friendly_name_length))) || + (g_str_has_prefix(from_name, log->account->alias) && + !isalnum(*(from_name + alias_length)))); + + to_name_matches = ((g_str_has_prefix( + to_name, friendly_name) && + !isalnum(*(to_name + friendly_name_length))) || + (g_str_has_prefix(to_name, log->account->alias) && + !isalnum(*(to_name + alias_length)))); + + if (from_name_matches) { + if (!to_name_matches) { + name_guessed = NAME_GUESS_ME; + } + } else if (to_name_matches) { + name_guessed = NAME_GUESS_THEM; + } else { + if (buddy) { + if (buddy->alias) { + char *alias = g_strdup(buddy->alias); + + /* "Truncate" the string at the first non-alphanumeric + * character. The idea is to relax the comparison. + */ + char *temp; + for (temp = alias; *temp ; temp++) { + if (!isalnum(*temp)) { + *temp = '\0'; + break; + } + } + alias_length = strlen(alias); + + /* Try to guess which user is them. + * The first step is to determine if either of the names + * matches their alias. For this test, "match" is + * defined as: ^alias([^a-zA-Z0-9].*)?$ + */ + from_name_matches = (g_str_has_prefix( + from_name, alias) && + !isalnum(*(from_name + + alias_length))); + + to_name_matches = (g_str_has_prefix( + to_name, alias) && + !isalnum(*(to_name + + alias_length))); + + g_free(alias); + + if (from_name_matches) { + if (!to_name_matches) { + name_guessed = NAME_GUESS_THEM; + } + } else if (to_name_matches) { + name_guessed = NAME_GUESS_ME; + } else if (buddy->server_alias) { + friendly_name_length = + strlen(buddy->server_alias); + + /* Try to guess which user is them. + * The first step is to determine if either of + * the names matches their friendly name. For + * this test, "match" is defined as: + * ^friendly_name([^a-zA-Z0-9].*)?$ + */ + from_name_matches = (g_str_has_prefix( + from_name, + buddy->server_alias) && + !isalnum(*(from_name + + friendly_name_length))); + + to_name_matches = (g_str_has_prefix( + to_name, buddy->server_alias) && + !isalnum(*(to_name + + friendly_name_length))); + + if (from_name_matches) { + if (!to_name_matches) { + name_guessed = NAME_GUESS_THEM; + } + } else if (to_name_matches) { + name_guessed = NAME_GUESS_ME; + } + } + } + } + } + } + + if (name_guessed != NAME_GUESS_UNKNOWN) { + text = g_string_append(text, "<span style=\"color: #"); + if (name_guessed == NAME_GUESS_ME) + text = g_string_append(text, "16569E"); + else + text = g_string_append(text, "A82F2F"); + text = g_string_append(text, ";\">"); + } + + time_unix = msn_logger_parse_timestamp(message); + tm_new = localtime(&time_unix); + + timestamp = g_strdup_printf("<font size=\"2\">(%02u:%02u:%02u)</font> ", + tm_new->tm_hour, tm_new->tm_min, tm_new->tm_sec); + text = g_string_append(text, timestamp); + g_free(timestamp); + + if (from_name) { + text = g_string_append(text, "<b>"); + + if (name_guessed == NAME_GUESS_ME) + text = g_string_append(text, log->account->alias); + else if (name_guessed == NAME_GUESS_THEM) + text = g_string_append(text, their_name); + else + text = g_string_append(text, from_name); + + text = g_string_append(text, ":</b> "); + } + + if (name_guessed != NAME_GUESS_UNKNOWN) + text = g_string_append(text, "</span>"); + + style = xmlnode_get_attrib(text_node, "Style"); + + if (style && *style) { + text = g_string_append(text, "<span style=\""); + text = g_string_append(text, style); + text = g_string_append(text, "\">"); + text = g_string_append(text, xmlnode_get_data(text_node)); + text = g_string_append(text, "</span>\n"); + } else { + text = g_string_append(text, xmlnode_get_data(text_node)); + text = g_string_append(text, "\n"); + } + } + + data->text = text; + + return text->str; +} + +static int msn_logger_size (GaimLog *log) +{ + char *text; + size_t size; + + g_return_val_if_fail(log != NULL, 0); + + if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) + return 0; + + text = msn_logger_read(log, NULL); + size = strlen(text); + g_free(text); + + return size; +} + +static void msn_logger_finalize(GaimLog *log) +{ + struct msn_logger_data *data; + + g_return_if_fail(log != NULL); + + data = log->logger_data; + + if (data->last_log) + xmlnode_free(data->root); + + if (data->text) + g_string_free(data->text, FALSE); +} + +static GaimLogLogger msn_logger = { + N_("MSN Log Reader"), "msn", + NULL, NULL, + msn_logger_finalize, + msn_logger_list, + msn_logger_read, + msn_logger_size, + NULL, + NULL, + NULL +}; + + +/***************************************************************************** + * Trillian Logger * + *****************************************************************************/ + +/* The trillian logger doesn't write logs, only reads them. This is to include + * Trillian logs in the log viewer transparently. + */ + +static GaimLogLogger trillian_logger; +static void trillian_logger_finalize(GaimLog *log); + +struct trillian_logger_data { + char *path; /* FIXME: Change this to use GaimStringref like log.c:old_logger_list */ + int offset; + int length; + char *their_nickname; +}; + +static GList *trillian_logger_list(GaimLogType type, const char *sn, GaimAccount *account) +{ + GList *list = NULL; + const char *logdir; + GaimPlugin *plugin; + GaimPluginProtocolInfo *prpl_info; + char *prpl_name; + const char *buddy_name; + char *filename; + char *path; + GError *error = NULL; + gchar *contents = NULL; + gsize length; + gchar *line; + gchar *c; + + g_return_val_if_fail(sn != NULL, list); + g_return_val_if_fail(account != NULL, list); + + logdir = gaim_prefs_get_string("/plugins/core/log_reader/trillian/log_directory"); + + /* By clearing the log directory path, this logger can be (effectively) disabled. */ + if (!*logdir) + return list; + + plugin = gaim_find_prpl(gaim_account_get_protocol_id(account)); + if (!plugin) + return NULL; + + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(plugin); + if (!prpl_info->list_icon) + return NULL; + + prpl_name = g_ascii_strup(prpl_info->list_icon(account, NULL), -1); + + buddy_name = gaim_normalize(account, sn); + + filename = g_strdup_printf("%s.log", buddy_name); + path = g_build_filename( + logdir, prpl_name, filename, NULL); + + gaim_debug(GAIM_DEBUG_INFO, "Trillian log list", + "Reading %s\n", path); + /* FIXME: There's really no need to read the entire file at once. + * See src/log.c:old_logger_list for a better approach. + */ + if (!g_file_get_contents(path, &contents, &length, &error)) { + if (error) { + g_error_free(error); + error = NULL; + } + g_free(path); + + path = g_build_filename( + logdir, prpl_name, "Query", filename, NULL); + gaim_debug(GAIM_DEBUG_INFO, "Trillian log list", + "Reading %s\n", path); + if (!g_file_get_contents(path, &contents, &length, &error)) { + if (error) + g_error_free(error); + } + } + g_free(filename); + + if (contents) { + struct trillian_logger_data *data = NULL; + int offset = 0; + int last_line_offset = 0; + + line = contents; + c = contents; + while (*c) { + offset++; + + if (*c != '\n') { + c++; + continue; + } + + *c = '\0'; + if (g_str_has_prefix(line, "Session Close ")) { + if (data && !data->length) + data->length = last_line_offset - data->offset; + if (!data->length) { + /* This log had no data, so we remove it. */ + GList *last = g_list_last(list); + + gaim_debug(GAIM_DEBUG_INFO, "Trillian log list", + "Empty log. Offset %i\n", data->offset); + + trillian_logger_finalize((GaimLog *)last->data); + list = g_list_delete_link(list, last); + } + } else if (line[0] && line[1] && line [3] && + g_str_has_prefix(&line[3], "sion Start ")) { + + char *their_nickname = line; + char *timestamp; + + if (data && !data->length) + data->length = last_line_offset - data->offset; + + while (*their_nickname && (*their_nickname != ':')) + their_nickname++; + their_nickname++; + + /* This code actually has nothing to do with + * the timestamp YET. I'm simply using this + * variable for now to NUL-terminate the + * their_nickname string. + */ + timestamp = their_nickname; + while (*timestamp && *timestamp != ')') + timestamp++; + + if (*timestamp == ')') { + char *month; + struct tm tm; + + *timestamp = '\0'; + if (line[0] && line[1] && line[2]) + timestamp += 3; + + /* Now we start dealing with the timestamp. */ + + /* Skip over the day name. */ + while (*timestamp && (*timestamp != ' ')) + timestamp++; + *timestamp = '\0'; + timestamp++; + + /* Parse out the month. */ + month = timestamp; + while (*timestamp && (*timestamp != ' ')) + timestamp++; + *timestamp = '\0'; + timestamp++; + + /* Parse the day, time, and year. */ + if (sscanf(timestamp, "%u %u:%u:%u %u", + &tm.tm_mday, &tm.tm_hour, + &tm.tm_min, &tm.tm_sec, + &tm.tm_year) != 5) { + + gaim_debug(GAIM_DEBUG_ERROR, + "Trillian log timestamp parse", + "Session Start parsing error\n"); + } else { + GaimLog *log; + + tm.tm_year -= 1900; + + /* Let the C library deal with + * daylight savings time. + */ + tm.tm_isdst = -1; + + /* Ugly hack, in case current locale + * is not English. This code is taken + * from log.c. + */ + 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; + } + + data = g_new0( + struct trillian_logger_data, 1); + data->path = g_strdup(path); + data->offset = offset; + data->length = 0; + data->their_nickname = + g_strdup(their_nickname); + + log = gaim_log_new(GAIM_LOG_IM, + sn, account, NULL, mktime(&tm)); + log->logger = &trillian_logger; + log->logger_data = data; + + list = g_list_append(list, log); + } + } + } + c++; + line = c; + last_line_offset = offset; + } + + g_free(contents); + } + g_free(path); + + g_free(prpl_name); + + return list; +} + +static char * trillian_logger_read (GaimLog *log, GaimLogReadFlags *flags) +{ + struct trillian_logger_data *data; + char *read; + FILE *file; + GaimBuddy *buddy; + char *escaped; + GString *formatted; + char *c; + char *line; + + g_return_val_if_fail(log != NULL, g_strdup("")); + + data = log->logger_data; + + g_return_val_if_fail(data->path != NULL, g_strdup("")); + g_return_val_if_fail(data->length > 0, g_strdup("")); + g_return_val_if_fail(data->their_nickname != NULL, g_strdup("")); + + gaim_debug(GAIM_DEBUG_INFO, "Trillian log read", + "Reading %s\n", data->path); + + read = g_malloc(data->length + 2); + + file = fopen(data->path, "rb"); + fseek(file, data->offset, SEEK_SET); + fread(read, data->length, 1, file); + fclose(file); + + if (read[data->length-1] == '\n') { + read[data->length] = '\0'; + } else { + read[data->length] = '\n'; + read[data->length+1] = '\0'; + } + + /* Load miscellaneous data. */ + buddy = gaim_find_buddy(log->account, log->name); + + escaped = g_markup_escape_text(read, -1); + g_free(read); + read = escaped; + + /* Apply formatting... */ + formatted = g_string_new(""); + c = read; + line = read; + while (*c) + { + if (*c == '\n') + { + char *link_temp_line; + char *link; + char *timestamp; + char *footer = NULL; + *c = '\0'; + + /* Convert links. + * + * The format is (Link: URL)URL + * So, I want to find each occurance of "(Link: " and replace that chunk with: + * <a href=" + * Then, replace the next ")" with: + * "> + * Then, replace the next " " (or add this if the end-of-line is reached) with: + * </a> + */ + link_temp_line = NULL; + while ((link = g_strstr_len(line, strlen(line), "(Link: "))) { + GString *temp; + + if (!*link) + continue; + + *link = '\0'; + link++; + + temp = g_string_new(line); + g_string_append(temp, "<a href=\""); + + if (strlen(link) >= 6) { + link += (sizeof("(Link: ") - 1); + + while (*link && *link != ')') { + g_string_append_c(temp, *link); + link++; + } + if (link) { + link++; + + g_string_append(temp, "\">"); + while (*link && *link != ' ') { + g_string_append_c(temp, *link); + link++; + } + g_string_append(temp, "</a>"); + } + + g_string_append(temp, link); + + /* Free the last round's line. */ + if (link_temp_line) + g_free(line); + + line = temp->str; + g_string_free(temp, FALSE); + + /* Save this memory location so we can free it later. */ + link_temp_line = line; + } + } + + timestamp = ""; + if (*line == '[') { + timestamp = line; + while (*timestamp && *timestamp != ']') + timestamp++; + if (*timestamp == ']') { + *timestamp = '\0'; + line++; + // TODO: Parse the timestamp and convert it to Gaim's format. + g_string_append_printf(formatted, + "<font size=\"2\">(%s)</font> ", line); + line = timestamp; + if (line[1] && line[2]) + line += 2; + } + + if (g_str_has_prefix(line, "*** ")) { + line += (sizeof("*** ") - 1); + g_string_append(formatted, "<b>"); + footer = "</b>"; + if (g_str_has_prefix(line, "NOTE: This user is offline.")) { + line = _("User is offline."); + } else if (g_str_has_prefix(line, + "NOTE: Your status is currently set to ")) { + + line += (sizeof("NOTE: ") - 1); + } else if (g_str_has_prefix(line, "Auto-response sent to ")) { + g_string_append(formatted, _("Auto-response sent:")); + while (*line && *line != ':') + line++; + if (*line) + line++; + g_string_append(formatted, "</b>"); + footer = NULL; + } else if (strstr(line, " signed off ")) { + if (buddy->alias) + g_string_append_printf(formatted, + _("%s logged out."), buddy->alias); + else + g_string_append_printf(formatted, + _("%s logged out."), log->name); + line = ""; + } else if (strstr(line, " signed on ")) { + if (buddy->alias) + g_string_append(formatted, buddy->alias); + else + g_string_append(formatted, log->name); + line = " logged in."; + } else if (g_str_has_prefix(line, + "One or more messages may have been undeliverable.")) { + + g_string_append(formatted, + "<span style=\"color: #ff0000;\">"); + g_string_append(formatted, + _("One or more messages may have been " + "undeliverable.")); + line = ""; + footer = "</span></b>"; + } else if (g_str_has_prefix(line, + "You have been disconnected.")) { + + g_string_append(formatted, + "<span style=\"color: #ff0000;\">"); + g_string_append(formatted, + _("You were disconnected from the server.")); + line = ""; + footer = "</span></b>"; + } else if (g_str_has_prefix(line, + "You are currently disconnected.")) { + + g_string_append(formatted, + "<span style=\"color: #ff0000;\">"); + line = _("You are currently disconnected. Messages " + "will not be received unless you are " + "logged in."); + footer = "</span></b>"; + } else if (g_str_has_prefix(line, + "Your previous message has not been sent.")) { + + g_string_append(formatted, + "<span style=\"color: #ff0000;\">"); + + if (g_str_has_prefix(line, + "Your previous message has not been sent. " + "Reason: Maximum length exceeded.")) { + + g_string_append(formatted, + _("Message could not be sent because " + "the maximum length was exceeded.")); + line = ""; + } else { + g_string_append(formatted, + _("Message could not be sent.")); + line += (sizeof( + "Your previous message " + "has not been sent. ") - 1); + } + + footer = "</span></b>"; + } + } else if (g_str_has_prefix(line, data->their_nickname)) { + if (buddy->alias) { + line += strlen(data->their_nickname) + 2; + g_string_append_printf(formatted, + "<span style=\"color: #A82F2F;\">" + "<b>%s</b></span>: ", buddy->alias); + } + } else { + char *line2 = line; + while (*line2 && *line2 != ':') + line2++; + if (*line2 == ':') { + line2++; + line = line2; + g_string_append_printf(formatted, + "<span style=\"color: #16569E;\">" + "<b>%s</b></span>:", log->account->alias); + } + } + } + + g_string_append(formatted, line); + + if (footer) + g_string_append(formatted, footer); + + g_string_append_c(formatted, '\n'); + + if (link_temp_line) + g_free(link_temp_line); + + c++; + line = c; + } else + c++; + } + + g_free(read); + read = formatted->str; + g_string_free(formatted, FALSE); + + return read; +} + +static int trillian_logger_size (GaimLog *log) +{ + struct trillian_logger_data *data; + char *text; + size_t size; + + g_return_val_if_fail(log != NULL, 0); + + data = log->logger_data; + + if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) { + return data ? data->length : 0; + } + + text = trillian_logger_read(log, NULL); + size = strlen(text); + g_free(text); + + return size; +} + +static void trillian_logger_finalize(GaimLog *log) +{ + struct trillian_logger_data *data; + + g_return_if_fail(log != NULL); + + data = log->logger_data; + + g_free(data->path); + g_free(data->their_nickname); + +} + +static GaimLogLogger trillian_logger = { + N_("Trillian Log Reader"), "trillian", + NULL, NULL, + trillian_logger_finalize, + trillian_logger_list, + trillian_logger_read, + trillian_logger_size, + NULL, + NULL, + NULL +}; + + +/***************************************************************************** + * Plugin Code * + *****************************************************************************/ + +static void +init_plugin(GaimPlugin *plugin) +{ + char *path; +#ifdef _WIN32 + char *folder; +#endif + + g_return_if_fail(plugin != NULL); + + gaim_prefs_add_none("/plugins/core/log_reader"); + + + /* Add general preferences. */ + + gaim_prefs_add_bool("/plugins/core/log_reader/fast_sizes", FALSE); + gaim_prefs_add_bool("/plugins/core/log_reader/use_name_heuristics", TRUE); + + + /* Add Adium log directory preference. */ + gaim_prefs_add_none("/plugins/core/log_reader/adium"); + + /* Calculate default Adium log directory. */ +#ifdef _WIN32 + path = ""; +#else + path = g_build_filename(gaim_home_dir(), "Library", "Application Support", + "Adium 2.0", "Users", "Default", "Logs", NULL); +#endif + + gaim_prefs_add_string("/plugins/core/log_reader/adium/log_directory", path); + +#ifndef _WIN32 + g_free(path); +#endif + + + /* Add Fire log directory preference. */ + gaim_prefs_add_none("/plugins/core/log_reader/fire"); + + /* Calculate default Fire log directory. */ +#ifdef _WIN32 + path = ""; +#else + path = g_build_filename(gaim_home_dir(), "Library", "Application Support", + "Fire", "Sessions", NULL); +#endif + + gaim_prefs_add_string("/plugins/core/log_reader/fire/log_directory", path); + +#ifndef _WIN32 + g_free(path); +#endif + + + /* Add Messenger Plus! log directory preference. */ + gaim_prefs_add_none("/plugins/core/log_reader/messenger_plus"); + + /* Calculate default Messenger Plus! log directory. */ +#ifdef _WIN32 + folder = wgaim_get_special_folder(CSIDL_PERSONAL); + if (folder) { +#endif + path = g_build_filename( +#ifdef _WIN32 + folder, +#else + LOG_READER_WINDOWS_MOUNT_POINT, "Documents and Settings", + g_get_user_name(), "My Documents", +#endif + "My Chat Logs", NULL); +#ifdef _WIN32 + g_free(folder); + } else /* !folder */ + path = g_strdup(""); +#endif + + gaim_prefs_add_string("/plugins/core/log_reader/messenger_plus/log_directory", path); + g_free(path); + + + /* Add MSN Messenger log directory preference. */ + gaim_prefs_add_none("/plugins/core/log_reader/msn"); + + /* Calculate default MSN message history directory. */ +#ifdef _WIN32 + folder = wgaim_get_special_folder(CSIDL_PERSONAL); + if (folder) { +#endif + path = g_build_filename( +#ifdef _WIN32 + folder, +#else + LOG_READER_WINDOWS_MOUNT_POINT, "Documents and Settings", + g_get_user_name(), "My Documents", +#endif + "My Received Files", NULL); +#ifdef _WIN32 + g_free(folder); + } else /* !folder */ + path = g_strdup(""); +#endif + + gaim_prefs_add_string("/plugins/core/log_reader/msn/log_directory", path); + g_free(path); + + + /* Add Trillian log directory preference. */ + gaim_prefs_add_none("/plugins/core/log_reader/trillian"); + +#ifdef _WIN32 + /* XXX: While a major hack, this is the most reliable way I could + * think of to determine the Trillian installation directory. + */ + HKEY hKey; + char buffer[1024] = ""; + DWORD size = (sizeof(buffer) - 1); + DWORD type; + + path = NULL; + /* TODO: Test this after removing the trailing "\\". */ + if(ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, "Trillian.SkinZip\\shell\\Add\\command\\", + 0, KEY_QUERY_VALUE, &hKey)) { + + if(ERROR_SUCCESS == RegQueryValueEx(hKey, "", NULL, &type, (LPBYTE)buffer, &size)) { + char *value = buffer; + char *temp; + + /* Ensure the data is null terminated. */ + value[size] = '\0'; + + /* Break apart buffer. */ + if (*value == '"') { + value++; + temp = value; + while (*temp && *temp != '"') + temp++; + } else { + temp = value; + while (*temp && *temp != ' ') + temp++; + } + *temp = '\0'; + + /* Set path. */ + if (g_str_has_suffix(value, "trillian.exe")) + { + value[strlen(value) - (sizeof("trillian.exe") - 1)] = '\0'; + path = g_build_filename(value, "users", "default", "talk.ini", NULL); + } + } + RegCloseKey(hKey); + } + + if (!path) { + char *folder = wgaim_get_special_folder(CSIDL_PROGRAM_FILES); + if (folder) + path = g_build_filename(folder, "Trillian", + "users", "default", "talk.ini", NULL); + g_free(folder); + } + } + + gboolean found = FALSE; + + if (path) { + /* Read talk.ini file to find the log directory. */ + GError *error = NULL; + +#if 0 && GTK_CHECK_VERSION(2,6,0) /* FIXME: Not tested yet. */ + GKeyFile *key_file; + + gaim_debug(GAIM_DEBUG_INFO, "Trillian talk.ini read", + "Reading %s\n", path); + if (!g_key_file_load_from_file(key_file, path, G_KEY_FILE_NONE, GError &error)) { + gaim_debug(GAIM_DEBUG_ERROR, "Trillian talk.ini read", + "Error reading talk.ini\n"); + if (error) + g_error_free(error); + } else { + char *logdir = g_key_file_get_string(key_file, "Logging", "Directory", &error); + if (error) { + gaim_debug(GAIM_DEBUG_ERROR, "Trillian talk.ini read", + "Error reading Directory value from Logging section\n"); + g_error_free(error); + } + + if (logdir) { + g_strchomp(logdir); + gaim_prefs_add_string( + "/plugins/core/log_reader/trillian/log_directory", logdir); + found = TRUE; + } + + g_key_file_free(key_file); + } +#else /* !GTK_CHECK_VERSION(2,6,0) */ + GError *error = NULL; + gsize length; + + gaim_debug(GAIM_DEBUG_INFO, "Trillian talk.ini read", + "Reading %s\n", path); + if (!g_file_get_contents(path, &contents, &length, &error)) { + gaim_debug(GAIM_DEBUG_ERROR, "Trillian talk.ini read", + "Error reading talk.ini\n"); + if (error) + g_error_free(error); + } else { + char *line = contents; + while (*contents) { + if (*contents == '\n') { + *contents = '\0'; + + /* XXX: This assumes the first Directory key is under [Logging]. */ + if (g_str_has_prefix(line, "Directory=")) { + line += (sizeof("Directory=") - 1); + g_strchomp(line); + gaim_prefs_add_string( + "/plugins/core/log_reader/trillian/log_directory", + line); + found = TRUE; + } + + contents++; + line = contents; + } else + contents++; + } + g_free(path); + g_free(contents); + } +#endif /* !GTK_CHECK_VERSION(2,6,0) */ + } /* path */ + + if (!found) { +#endif /* defined(_WIN32) */ + + /* Calculate default Trillian log directory. */ +#ifdef _WIN32 + folder = wgaim_get_special_folder(CSIDL_PROGRAM_FILES); + if (folder) { +#endif + path = g_build_filename( +#ifdef _WIN32 + folder, +#else + LOG_READER_WINDOWS_MOUNT_POINT, "Program Files", +#endif + "Trillian", "users", "default", "logs", NULL); +#ifdef _WIN32 + g_free(folder); + } else /* !folder */ + path = g_strdup(""); +#endif + + gaim_prefs_add_string("/plugins/core/log_reader/trillian/log_directory", path); + g_free(path); + +#ifdef _WIN32 + } /* !found */ +#endif +} + +static gboolean +plugin_load(GaimPlugin *plugin) +{ + g_return_val_if_fail(plugin != NULL, FALSE); + + gaim_log_logger_add(&adium_logger); + gaim_log_logger_add(&fire_logger); + gaim_log_logger_add(&messenger_plus_logger); + gaim_log_logger_add(&msn_logger); + gaim_log_logger_add(&trillian_logger); + + return TRUE; +} + +static gboolean +plugin_unload(GaimPlugin *plugin) +{ + g_return_val_if_fail(plugin != NULL, FALSE); + + gaim_log_logger_remove(&adium_logger); + gaim_log_logger_remove(&fire_logger); + gaim_log_logger_remove(&messenger_plus_logger); + gaim_log_logger_remove(&msn_logger); + gaim_log_logger_remove(&trillian_logger); + + return TRUE; +} + +static GaimPluginPrefFrame * +get_plugin_pref_frame(GaimPlugin *plugin) +{ + GaimPluginPrefFrame *frame; + GaimPluginPref *ppref; + + g_return_val_if_fail(plugin != NULL, FALSE); + + frame = gaim_plugin_pref_frame_new(); + + + /* Add general preferences. */ + + ppref = gaim_plugin_pref_new_with_label(_("General Log Reading Configuration")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_name_and_label( + "/plugins/core/log_reader/fast_sizes", _("Fast size calculations")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_name_and_label( + "/plugins/core/log_reader/use_name_heuristics", _("Use name heuristics")); + gaim_plugin_pref_frame_add(frame, ppref); + + + /* Add Log Directory preferences. */ + + ppref = gaim_plugin_pref_new_with_label(_("Log Directory")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_name_and_label( + "/plugins/core/log_reader/adium/log_directory", _("Adium")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_name_and_label( + "/plugins/core/log_reader/fire/log_directory", _("Fire")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_name_and_label( + "/plugins/core/log_reader/messenger_plus/log_directory", _("Messenger Plus!")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_name_and_label( + "/plugins/core/log_reader/msn/log_directory", _("MSN Messenger")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_name_and_label( + "/plugins/core/log_reader/trillian/log_directory", _("Trillian")); + gaim_plugin_pref_frame_add(frame, ppref); + + return frame; +} + +static GaimPluginUiInfo prefs_info = { + get_plugin_pref_frame +}; + +static GaimPluginInfo info = +{ + GAIM_PLUGIN_MAGIC, + GAIM_MAJOR_VERSION, + GAIM_MINOR_VERSION, + GAIM_PLUGIN_STANDARD, /**< type */ + NULL, /**< ui_requirement */ + 0, /**< flags */ + NULL, /**< dependencies */ + GAIM_PRIORITY_DEFAULT, /**< priority */ + "core-log_reader", /**< id */ + N_("Log Reader"), /**< name */ + VERSION, /**< version */ + + /** summary */ + N_("Includes other IM clients' logs in the " + "log viewer."), + + /** description */ + N_("When viewing logs, this plugin will include " + "logs from other IM clients. Currently, this " + "includes Adium, Fire, Messenger Plus!, " + "MSN Messenger, and Trillian."), + + "Richard Laager <rlaager@bigfoot.com>", /**< author */ + GAIM_WEBSITE, /**< homepage */ + plugin_load, /**< load */ + plugin_unload, /**< unload */ + NULL, /**< destroy */ + NULL, /**< ui_info */ + NULL, /**< extra_info */ + &prefs_info, /**< prefs_info */ + NULL /**< actions */ +}; + +GAIM_INIT_PLUGIN(log_reader, init_plugin, info)