Mercurial > pidgin
changeset 11025:8d2007d738d5
[gaim-migrate @ 12899]
sf patch #1180490, from Richard Laager (who else?)
A pretty peach of a patch that allows you to auto-complete screen names
based on log file names.
committer: Tailor Script <tailor@pidgin.im>
author | Mark Doliner <mark@kingant.net> |
---|---|
date | Thu, 23 Jun 2005 03:04:52 +0000 |
parents | 1d58cc6c4552 |
children | a659e6bac294 |
files | plugins/ChangeLog.API src/gtkblist.c src/gtkdialogs.c src/gtkrequest.c src/log.c src/log.h |
diffstat | 6 files changed, 439 insertions(+), 30 deletions(-) [+] |
line wrap: on
line diff
--- a/plugins/ChangeLog.API Thu Jun 23 02:24:22 2005 +0000 +++ b/plugins/ChangeLog.API Thu Jun 23 03:04:52 2005 +0000 @@ -60,6 +60,10 @@ * Added: gaim_gtk_blist_node_is_contact_expanded, returns TRUE if the given blist node is a buddy inside an expanded contact, or is itself an expanded contact + * Added: GaimLogSet struct, get_log_sets function to GaimLogLogger, + gaim_log_get_log_sets, gaim_log_set_compare + * Changed: gaim_log_logger_new, added total_size, list_syslog, and get_log_sets + parameters to bring the function up-to-date with GaimLogLogger Signals: * Changed: "received-im-msg" and "received-chat-msg" to match, both
--- a/src/gtkblist.c Thu Jun 23 02:24:22 2005 +0000 +++ b/src/gtkblist.c Thu Jun 23 03:04:52 2005 +0000 @@ -2660,8 +2660,8 @@ { N_("/Buddies/Get User _Info..."), "<CTL>I", gaim_gtkdialogs_info, 0, "<StockItem>", GAIM_STOCK_INFO }, { N_("/Buddies/View User _Log..."), "<CTL>L", gaim_gtkdialogs_log, 0, NULL }, { "/Buddies/sep1", NULL, NULL, 0, "<Separator>" }, - { N_("/Buddies/Show _Offline Buddies"), "<CTL>O", gaim_gtk_blist_edit_mode_cb, 1, "<CheckItem>"}, - { N_("/Buddies/Show _Empty Groups"), "<CTL>E", gaim_gtk_blist_show_empty_groups_cb, 1, "<CheckItem>"}, + { N_("/Buddies/Show _Offline Buddies"), NULL, gaim_gtk_blist_edit_mode_cb, 1, "<CheckItem>"}, + { N_("/Buddies/Show _Empty Groups"), NULL, gaim_gtk_blist_show_empty_groups_cb, 1, "<CheckItem>"}, { N_("/Buddies/_Add Buddy..."), "<CTL>B", gaim_gtk_blist_add_buddy_cb, 0, "<StockItem>", GTK_STOCK_ADD }, { N_("/Buddies/Add C_hat..."), NULL, gaim_gtk_blist_add_chat_cb, 0, "<StockItem>", GTK_STOCK_ADD }, { N_("/Buddies/Add _Group..."), NULL, gaim_blist_request_add_group, 0, "<StockItem>", GTK_STOCK_ADD },
--- a/src/gtkdialogs.c Thu Jun 23 02:24:22 2005 +0000 +++ b/src/gtkdialogs.c Thu Jun 23 03:04:52 2005 +0000 @@ -694,7 +694,7 @@ gaim_request_fields_add_group(fields, group); field = gaim_request_field_string_new("screenname", _("_Name"), NULL, FALSE); - gaim_request_field_set_type_hint(field, "screenname"); + gaim_request_field_set_type_hint(field, "screenname-all"); gaim_request_field_set_required(field, TRUE); gaim_request_field_group_add_field(group, field); @@ -707,7 +707,7 @@ gaim_request_field_set_required(field, TRUE); gaim_request_field_group_add_field(group, field); - gaim_request_fields(gaim_get_blist(), _("Get User Log"), + gaim_request_fields(gaim_get_blist(), _("View User Log"), NULL, _("Please enter the screen name or alias of the person " "whose log you would like to view."),
--- a/src/gtkrequest.c Thu Jun 23 02:24:22 2005 +0000 +++ b/src/gtkrequest.c Thu Jun 23 03:04:52 2005 +0000 @@ -648,10 +648,12 @@ } static GList * -get_online_names(void) +get_screenname_completion_data(gboolean all) { GList *names = NULL; GaimBlistNode *gnode, *cnode, *bnode; + GList *log_sets; + GList *log_set; for (gnode = gaim_get_blist()->root; gnode != NULL; gnode = gnode->next) { @@ -667,7 +669,7 @@ { GaimBuddy *buddy = (GaimBuddy *)bnode; - if (!gaim_account_is_connected(buddy->account)) + if (!all && !gaim_account_is_connected(buddy->account)) continue; #ifdef NEW_STYLE_COMPLETION @@ -677,11 +679,34 @@ names = g_list_append(names, buddy->account); #endif /* NEW_STYLE_COMPLETION */ - names = g_list_append(names, buddy->name); + names = g_list_append(names, g_strdup(buddy->name)); } } } + log_sets = gaim_log_get_log_sets(); + for (log_set = log_sets ; log_set != NULL ; log_set = log_set->next) { + GaimLogSet *set = log_set->data; + + /* 1. Don't show buddies because we will have gotten them above. + * 2. If we're not showing all accounts, then only show those with + * non-NULL accounts that are currently connected. + * 3. The boxes that use this autocomplete code handle only IMs. */ + if (!set->buddy && + (all || (set->account != NULL && gaim_account_is_connected(set->account))) && + set->type != GAIM_LOG_CHAT) { +#ifdef NEW_STYLE_COMPLETION + names = g_list_append(names, NULL); + names = g_list_append(names, NULL); + names = g_list_append(names, set->account); +#endif /* NEW_STYLE_COMPLETION */ + + names = g_list_append(names, set->name); + } + + g_free(set); + } + return names; } @@ -759,6 +784,7 @@ static void destroy_completion_data(GtkWidget *w, GaimGtkCompletionData *data) { + g_list_foreach(data->completion->items, (GFunc)g_free, NULL); g_completion_free(data->completion); g_free(data); @@ -861,23 +887,23 @@ #endif /* !NEW_STYLE_COMPLETION */ static void -setup_screenname_autocomplete(GtkWidget *entry, GaimRequestField *field) +setup_screenname_autocomplete(GtkWidget *entry, GaimRequestField *field, gboolean all) { #ifdef NEW_STYLE_COMPLETION GtkListStore *store; GtkTreeIter iter; GtkEntryCompletion *completion; - GList *aliases_and_screennames; + GList *screenname_completion_data; GList *l; gpointer *data; - /* Store the displayed completion value, the screenname, and the value for comparison. */ + /* Store the displayed completion value, the screenname, the value for comparison, and the account. */ store = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER); - aliases_and_screennames = get_online_names(); + screenname_completion_data = get_screenname_completion_data(all); /* Loop through the list four elements at a time. */ - for (l = aliases_and_screennames; l != NULL; l = l->next->next->next->next) + for (l = screenname_completion_data; l != NULL; l = l->next->next->next->next) { char *contact_alias = l->data; char *buddy_alias = l->next->data; @@ -929,9 +955,15 @@ 3, account, -1); } + + g_free(screenname); } - g_list_free(aliases_and_screennames); + g_list_free(screenname_completion_data); + + /* Sort the completion list by screenname. */ + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), + 1, GTK_SORT_ASCENDING); completion = gtk_entry_completion_new(); gtk_entry_completion_set_match_func(completion, screenname_completion_match_func, NULL, NULL); @@ -960,7 +992,7 @@ g_completion_set_compare(data->completion, g_ascii_strncasecmp); - screennames = get_online_names(); + screennames = get_screenname_completion_data(all); g_completion_add_items(data->completion, screennames); @@ -989,9 +1021,9 @@ if ((type_hint = gaim_request_field_get_type_hint(field)) != NULL) { - if (!strcmp(type_hint, "screenname")) + if (!strncmp(type_hint, "screenname", sizeof("screenname") - 1)) { - setup_screenname_autocomplete(entry, field); + setup_screenname_autocomplete(entry, field, !strcmp(type_hint, "screenname-all")); } } }
--- a/src/log.c Thu Jun 23 02:24:22 2005 +0000 +++ b/src/log.c Thu Jun 23 03:04:52 2005 +0000 @@ -43,6 +43,7 @@ }; static GHashTable *logsize_users = NULL; +static GList *log_get_log_sets_common(); /************************************************************************** * PUBLIC LOGGING FUNCTIONS *********************************************** @@ -238,15 +239,23 @@ void(*finalize)(GaimLog *), GList*(*list)(GaimLogType type, const char*, GaimAccount*), char*(*read)(GaimLog*, GaimLogReadFlags*), - int(*size)(GaimLog*)) + int(*size)(GaimLog*), + int(*total_size)(GaimLogType type, const char *name, GaimAccount *account), + GList*(*list_syslog)(GaimAccount *account), + GList*(*get_log_sets)(void)) { GaimLogLogger *logger = g_new0(GaimLogLogger, 1); + logger->create = create; logger->write = write; logger->finalize = finalize; logger->list = list; logger->read = read; logger->size = size; + logger->total_size = total_size; + logger->list_syslog = list_syslog; + logger->get_log_sets = get_log_sets; + return logger; } @@ -319,6 +328,103 @@ 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; + char *tmp; + + /* 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 = gaim_utf8_strcasecmp(gaim_account_get_username(a->account), gaim_account_get_username(b->account)); + if (ret != 0) + return ret; + } + + tmp = g_strdup(gaim_normalize(a->account, a->name)); + ret = gaim_utf8_strcasecmp(tmp, gaim_normalize(b->account, b->name)); + g_free(tmp); + if (ret != 0) + return ret; + + return (gint)b->type - (gint)a->type; +} + +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); +} + +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); +} + +void log_set_build_list(gpointer key, gpointer value, gpointer user_data) +{ + *((GList **)user_data) = g_list_append(*((GList **)user_data), key); +} + +GList *gaim_log_get_log_sets() +{ + GSList *n; + GList *sets = NULL; + GList *set; + GHashTable *sets_ht = g_hash_table_new(log_set_hash, log_set_equal); + + /* 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; + + sets = g_list_concat(sets, logger->get_log_sets()); + } + + /* Get the log sets for loggers that use the common logger functions. */ + sets = g_list_concat(sets, log_get_log_sets_common()); + + for (set = sets; set != NULL ; set = set->next) { + GaimLogSet *existing_set = g_hash_table_lookup(sets_ht, set->data); + + if (existing_set == NULL) { + g_hash_table_insert(sets_ht, set->data, set->data); + } else if (existing_set->account == NULL && ((GaimLogSet *)set->data)->account != NULL) { + /* The existing entry in the hash table has no account. + * This one does. We'll delete the old one and keep this one. */ + g_hash_table_replace(sets_ht, set->data, set->data); + g_free(existing_set->name); + g_free(existing_set); + } else { + g_free(((GaimLogSet *)set->data)->name); + g_free(set->data); + } + } + g_list_free(sets); + + /* At this point, we've built a GHashTable of unique GaimLogSets. + * So, we build a list of those keys and destroy the GHashTable. */ + sets = NULL; + g_hash_table_foreach(sets_ht, log_set_build_list, &sets); + g_hash_table_destroy(sets_ht); + + return g_list_sort(sets, gaim_log_set_compare); +} + GList *gaim_log_get_system_logs(GaimAccount *account) { GList *logs = NULL; @@ -446,6 +552,124 @@ 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 GList *log_get_log_sets_common() +{ + gchar *log_path = g_build_filename(gaim_user_dir(), "logs", NULL); + GDir *log_dir = g_dir_open(log_path, 0, NULL); + const gchar *protocol; + GList *sets = NULL; + + if (log_dir == NULL) { + g_free(log_path); + return NULL; + } + + 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_append(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 = g_new0(GaimLogSet, 1); + + /* Unescape the filename. */ + name = g_strdup(gaim_unescape_filename(name)); + + /* Get the (possibly new) length of name. */ + len = strlen(name); + + set->account = account; + set->name = name; + + /* Chat for .chat or .system at the end of the name to determine the type. */ + set->type = GAIM_LOG_IM; + 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 (gaim_find_buddy(account, name) != NULL) + set->buddy = TRUE; + else + set->buddy = FALSE; + + sets = g_list_append(sets, set); + } + g_free(username_path); + g_dir_close(username_dir); + } + g_free(protocol_path); + g_dir_close(protocol_dir); + } + g_free(log_path); + g_dir_close(log_dir); + + return sets; +} + #if 0 /* Maybe some other time. */ /**************** ** XML LOGGER ** @@ -541,6 +765,7 @@ xml_logger_finalize, xml_logger_list, NULL, + NULL, NULL }; #endif @@ -672,7 +897,8 @@ html_logger_read, gaim_log_common_sizer, NULL, - html_logger_list_syslog + html_logger_list_syslog, + NULL }; @@ -804,7 +1030,8 @@ txt_logger_read, gaim_log_common_sizer, NULL, - txt_logger_list_syslog + txt_logger_list_syslog, + NULL }; /**************** @@ -991,6 +1218,89 @@ return data ? data->length : 0; } +static GList *old_logger_get_log_sets() +{ + char *log_path = g_build_filename(gaim_user_dir(), "logs", NULL); + GDir *log_dir = g_dir_open(log_path, 0, NULL); + gchar *name; + GList *sets = NULL; + GaimBlistNode *gnode, *cnode, *bnode; + + g_free(log_path); + if (log_dir == NULL) + return NULL; + + /* 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; + } + + set = g_new0(GaimLogSet, 1); + + /* 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 = 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; + } + } + } + } + + sets = g_list_append(sets, set); + } + g_dir_close(log_dir); + + return sets; +} + static void old_logger_finalize(GaimLog *log) { struct old_logger_data *data = log->logger_data; @@ -1006,5 +1316,6 @@ old_logger_read, old_logger_size, old_logger_total_size, - NULL + NULL, + old_logger_get_log_sets };
--- a/src/log.h Thu Jun 23 02:24:22 2005 +0000 +++ b/src/log.h Thu Jun 23 03:04:52 2005 +0000 @@ -35,6 +35,7 @@ typedef struct _GaimLog GaimLog; typedef struct _GaimLogLogger GaimLogLogger; typedef struct _GaimLogCommonLoggerData GaimLogCommonLoggerData; +typedef struct _GaimLogSet GaimLogSet; typedef enum { GAIM_LOG_IM, @@ -91,6 +92,13 @@ /** This function returns a sorted GList of available system GaimLogs */ GList *(*list_syslog)(GaimAccount *account); + + /** Returns a list of GaimLogSets. By passing the data in the GaimLogSets + * to list, the caller can get every available GaimLog from the logger. + * Loggers using gaim_log_common_writer() (or otherwise storing their + * logs in the same directory structure as the stock loggers) do not + * need to implement this function. */ + GList *(*get_log_sets)(void); }; /** @@ -118,6 +126,26 @@ void *extra_data; }; +/** + * Describes available logs. + * + * By passing the elements of this struct to gaim_log_get_logs(), the caller + * can get all available GaimLogs. + */ +struct _GaimLogSet { + GaimLogType type; /**< The type of logs available */ + char *name; /**< The name of the logs available */ + GaimAccount *account; /**< The account the available logs + took place on. This will be + NULL if the account no longer + exists. (Depending on a + logger's implementation of + list, it may not be possible + to load such logs.) */ + gboolean buddy; /**< Is this (account, name) a buddy + on the buddy list? */ +}; + #ifdef __cplusplus extern "C" { #endif @@ -131,7 +159,7 @@ * Creates a new log * * @param type The type of log this is. - * @param name The name of this conversation (Screenname, chat name, + * @param name The name of this conversation (screenname, chat name, * etc.) * @param account The account the conversation is occurring on * @param time The time this conversation started @@ -184,6 +212,20 @@ GList *gaim_log_get_logs(GaimLogType type, const char *name, GaimAccount *account); /** + * Returns a list of GaimLogSets. + * + * A "log set" here means the information necessary to gather the + * GaimLogs for a given buddy/chat. This information would be passed + * to gaim_log_list to get a list of GaimLogs. + * + * The primary use of this function is to get a list of everyone the + * user has ever talked to (assuming he or she uses logging). + * + * @return A sorted list of all available unique GaimLogSets + */ +GList *gaim_log_get_log_sets(void); + +/** * Returns a list of all available system logs * * @param account The account @@ -222,13 +264,23 @@ char *gaim_log_get_log_dir(GaimLogType type, const char *name, GaimAccount *account); /** - * Implements GCompareFunc + * Implements GCompareFunc for GaimLogs * * @param y A GaimLog * @param z Another GaimLog * @return A value as specified by GCompareFunc */ gint gaim_log_compare(gconstpointer y, gconstpointer z); + +/** + * Implements GCompareFunc for GaimLogSets + * + * @param y A GaimLogSet + * @param z Another GaimLogSet + * @return A value as specified by GCompareFunc + */ +gint gaim_log_set_compare(gconstpointer y, gconstpointer z); + /*@}*/ /******************************************/ @@ -287,12 +339,15 @@ /** * Creates a new logger * - * @param create The logger's new function. - * @param write The logger's write function. - * @param finalize The logger's finalize function. - * @param list The logger's list function. - * @param read The logger's read function. - * @param size The logger's size function. + * @param create The logger's new function. + * @param write The logger's write function. + * @param finalize The logger's finalize function. + * @param list The logger's list function. + * @param read The logger's read function. + * @param size The logger's size function. + * @param total_size The logger's total_size function. + * @param list_syslog The logger's list_syslog function. + * @param get_log_sets The logger's get_log_sets function. * * @return The new logger */ @@ -302,7 +357,11 @@ void(*finalize)(GaimLog *), GList*(*list)(GaimLogType type, const char*, GaimAccount*), char*(*read)(GaimLog*, GaimLogReadFlags*), - int(*size)(GaimLog*)); + int(*size)(GaimLog*), + int(*total_size)(GaimLogType type, const char *name, GaimAccount *account), + GList*(*list_syslog)(GaimAccount *account), + GList*(*get_log_sets)(void)); + /** * Frees a logger * @@ -342,13 +401,16 @@ GaimLogLogger *gaim_log_logger_get (void); /** - * Returns a GList containing the IDs and Names of the registered log + * Returns a GList containing the IDs and names of the registered * loggers. * * @return The list of IDs and names. */ GList *gaim_log_logger_get_options(void); +/** + * Initializes the log subsystem. + */ void gaim_log_init(void); /*@}*/