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