changeset 80:e0bf37c105eb

work in progress one hash table code: - introduced icon_data structure to accommodate every icon related data. - added a common function remove_marks_func() which removes icon request mark. this function ought to be called from g_hash_table_foreach(). - added another new function cancel_fetch_func(), to clean up fetch requests at unload. this function also to be called from g_hash_table_foreach().
author Yoshiki Yazawa <yaz@cc.rim.or.jp>
date Tue, 01 Jul 2008 18:20:48 +0900
parents a4a6c7b204c9
children f1163f2e4920
files pidgin-twitter.c pidgin-twitter.h
diffstat 2 files changed, 167 insertions(+), 128 deletions(-) [+]
line wrap: on
line diff
--- a/pidgin-twitter.c	Mon Jun 30 11:02:01 2008 +0900
+++ b/pidgin-twitter.c	Tue Jul 01 18:20:48 2008 +0900
@@ -1,4 +1,3 @@
-
 /*
  * Pidgin-Twitter plugin.
  *
@@ -24,10 +23,15 @@
 /* globals */
 static GRegex *regp[7];
 static gboolean suppress_oops = FALSE;
-static GHashTable *icon_id_by_user;
-static GList *requested_users = NULL;
-static GList *requestings = NULL;
-static GHashTable *icon_mark_list_by_user;
+static GHashTable *icon_data_by_user = NULL;
+
+typedef struct icon_data {
+    gint icon_id;        // image id
+    gboolean requested;  // TRUE if download icon has been requested
+    GList *request_list; // marker list
+    PurpleUtilFetchUrlData *fetch_data;          // icon fetch data
+} icon_data;
+
 
 /* this function is a modified clone of purple_markup_strip_html() */
 static char *
@@ -275,7 +279,7 @@
 
 static void
 post_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
-                        const gchar *url_text, size_t len, 
+                        const gchar *url_text, size_t len,
                         const gchar *error_message)
 {
     twitter_message_t *tm = (struct twitter_message *)user_data;
@@ -285,7 +289,7 @@
     PurpleConversation *conv;
 
     conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY,
-                                                 "twitter@twitter.com", 
+                                                 "twitter@twitter.com",
                                                  tm->account);
     if (!conv) {
         twitter_debug("failed to get conversation\n");
@@ -302,7 +306,7 @@
              || strncmp(url_text, "HTTP/1.1", strlen("HTTP/1.1")) == 0)) {
 
             p1 = strchr(url_text, ' ');
-            
+
             if (p1) {
                 p1++;
                 p2 = strchr(p1, ' ');
@@ -314,7 +318,7 @@
         }
 
         code = atoi(p1);
-        
+
         if (code == 200) {
             error = 0;
         } else {
@@ -343,7 +347,7 @@
                                "Try again later.");
                 break;
             default:
-                msg = g_strdup_printf("Unknown error. (%d %s)", 
+                msg = g_strdup_printf("Unknown error. (%d %s)",
                                       code, p2 ? p2 : "");
                 break;
             }
@@ -359,7 +363,7 @@
             m = "";
 
         purple_conv_im_write(conv->u.im,
-                             purple_account_get_username(tm->account), 
+                             purple_account_get_username(tm->account),
                              m, PURPLE_MESSAGE_SEND, tm->time);
 
         g_free(m);
@@ -369,7 +373,7 @@
                             msg, tm->status);
         /* FIXME: too strong. it should be more smart */
         purple_conv_im_write(conv->u.im,
-                             purple_account_get_username(tm->account), 
+                             purple_account_get_username(tm->account),
                              m, PURPLE_MESSAGE_ERROR, time(NULL));
         g_free(m);
     }
@@ -377,7 +381,7 @@
  fin:
     if (msg)
         g_free(msg);
-    
+
     if (tm) {
         if (tm->status)
             g_free(tm->status);
@@ -392,13 +396,13 @@
     char *request, *status, *header;
     const char *url_encoded = purple_url_encode(*buffer);
     char *basic_auth, *basic_auth_encoded;
-    
+
     twitter_message_t *tm;
 
     const char *screen_name = purple_prefs_get_string(OPT_SCREEN_NAME);
     const char *password = purple_prefs_get_string(OPT_PASSWORD);
 
-    twitter_debug("tm.account: %s\n", 
+    twitter_debug("tm.account: %s\n",
                   purple_account_get_username(account));
 
     if (!screen_name || !password || !screen_name[0] || !password[0]) {
@@ -412,14 +416,14 @@
     tm->time = time(NULL);
 
     basic_auth = g_strdup_printf("%s:%s", screen_name, password);
-    basic_auth_encoded = purple_base64_encode((unsigned char *)basic_auth, 
+    basic_auth_encoded = purple_base64_encode((unsigned char *)basic_auth,
                                               strlen(basic_auth));
     g_free(basic_auth);
 
     status = g_strdup_printf(TWITTER_STATUS_FORMAT, url_encoded);
 
     header = g_strdup_printf(TWITTER_STATUS_POST, basic_auth_encoded,
-                             strlen(status));
+                             (int)strlen(status));
 
     request = g_strconcat(header, status, TWITTER_STATUS_TERMINATOR, NULL);
 
@@ -431,7 +435,7 @@
     g_free(basic_auth_encoded);
     g_free(status);
     g_free(request);
-    
+
 }
 
 static gboolean
@@ -700,38 +704,53 @@
 }
 
 static void
+remove_marks_func(gpointer key, gpointer value, gpointer user_data)
+{
+    icon_data *data = (icon_data *)value;
+    GtkTextBuffer *text_buffer = (GtkTextBuffer *)user_data;
+    GList *mark_list = NULL;
+    GList *current;
+
+    if(data && data->request_list)
+        mark_list = data->request_list;
+
+    /* remove the marks in its GtkTextBuffers */
+    for(current = g_list_first(mark_list); current;
+        current = g_list_next(current)) {
+        GtkTextMark *current_mark = current->data;
+        GtkTextBuffer *current_text_buffer = gtk_text_mark_get_buffer(
+            current_mark);
+
+        if(!current_text_buffer)
+            continue;
+
+        if(text_buffer) {
+            if(current_text_buffer == text_buffer) {
+                /* the mark will be freed in this function */
+                gtk_text_buffer_delete_mark(current_text_buffer,
+                                            current_mark);
+                current->data = NULL;
+            }
+        }
+        else {
+            gtk_text_buffer_delete_mark(current_text_buffer, current_mark);
+            current->data = NULL;
+        }
+    } /* end of for */
+
+    mark_list = g_list_remove_all(mark_list, NULL);
+    data->request_list = mark_list;
+}
+
+static void
 delete_requested_icon_marks(PidginConversation *conv) {
     GtkTextBuffer *text_buffer = gtk_text_view_get_buffer(
         GTK_TEXT_VIEW(conv->imhtml));
-    GList *user_name_list = g_hash_table_get_keys(icon_mark_list_by_user);
-    GList *current_user;  
-
-    for(current_user = g_list_first(user_name_list); current_user;
-            current_user = g_list_next(current_user)) { 
-        gchar *user_name = current_user->data;
-        GList *mark_list = g_hash_table_lookup(icon_mark_list_by_user,
-                                               user_name);
 
-        /* delete the marks in the buffer that will be closed. */
-        GList *current = g_list_first(mark_list);
-        while(current) {
-            GtkTextMark *mark = current->data;
-            GList *next = g_list_next(current);
-    
-            if(gtk_text_mark_get_buffer(mark) == text_buffer) {
-                /* the mark will be freed in the function */
-                gtk_text_buffer_delete_mark(text_buffer, mark);
-                mark_list = g_list_delete_link(mark_list, current);
-            }
-    
-            current = next;
-        }
+    g_hash_table_foreach(icon_data_by_user,
+                         (GHFunc)remove_marks_func,
+                         (gpointer)text_buffer);
 
-        /* Does not replace the old key */
-        g_hash_table_insert(icon_mark_list_by_user,
-                            g_strdup(user_name), mark_list);
-    }
-    g_list_free(user_name_list);
 }
 
 static void
@@ -943,7 +962,7 @@
             PurpleConversation *purple_conv = conv->active_conv;
 
             if(purple_conv && is_twitter_conv(purple_conv)) {
-                GtkIMHtml *current_imhtml = GTK_IMHTML(conv->imhtml); 
+                GtkIMHtml *current_imhtml = GTK_IMHTML(conv->imhtml);
                 GtkTextBuffer *current_buffer = gtk_text_view_get_buffer(
                      GTK_TEXT_VIEW(current_imhtml));
 
@@ -965,7 +984,12 @@
                                      &inserting_point, requested_mark);
 
     /* insert icon */
-    icon_id = GPOINTER_TO_INT(g_hash_table_lookup(icon_id_by_user, user_name));
+    icon_data *data = (icon_data *)g_hash_table_lookup(icon_data_by_user, user_name);
+    if(data)
+        icon_id = data->icon_id;
+    else
+        icon_id = 0;
+
     if(!icon_id) {
         return;
     }
@@ -973,16 +997,25 @@
     /* insert icon actually */
     gtk_imhtml_insert_image_at_iter(target_imhtml, icon_id, &inserting_point);
     gtk_text_buffer_delete_mark(target_buffer, requested_mark);
+    requested_mark = NULL;
+
 }
 
 static void
 insert_requested_icon(const gchar *user_name)
 {
-    GList *mark_list = g_hash_table_lookup(icon_mark_list_by_user, user_name);
+    icon_data *data = (icon_data *)g_hash_table_lookup(icon_data_by_user, user_name);
+    GList *mark_list = NULL;
+
+    if(data)
+        mark_list = data->request_list;
 
-    g_list_foreach(mark_list, (GFunc) insert_icon_at_mark, (gchar *)user_name);
-    g_hash_table_remove(icon_mark_list_by_user, user_name);
-    g_list_free(mark_list);
+    if(mark_list) {
+        g_list_foreach(mark_list, (GFunc) insert_icon_at_mark, (gchar *)user_name);
+        mark_list = g_list_remove_all(mark_list, NULL);
+        g_list_free(mark_list);
+        data->request_list = NULL;
+    }
 }
 
 static void
@@ -991,14 +1024,19 @@
 {
     gchar *user_name = (gchar *)user_data;
     int icon_id;
+    icon_data *data = NULL;
 
     twitter_debug("called\n");
 
-    requestings = g_list_remove(requestings, url_data);
+    data = (icon_data *)g_hash_table_lookup(icon_data_by_user, user_name);
+
+    if(data && data->fetch_data) {
+        data->fetch_data = NULL;
+    }
 
     /* Return if user's icon had already been downloaded or
      * the download is failure. */
-    if(g_hash_table_lookup(icon_id_by_user, user_name) || !url_text) {
+    if((data && data->icon_id) || !url_text) { //xxx
         if(!url_text) {
             twitter_debug("downloading %s's icon was failure : %s\n",
                           user_name, error_message);
@@ -1007,20 +1045,21 @@
             twitter_debug("%s's icon has already been downloaded\n", user_name);
         }
 
-        GList *deleting = g_list_find_custom(requested_users,
-                                             user_name, (GCompareFunc) strcmp);
-        if(deleting) {
-            g_free(deleting->data);
-            requested_users = g_list_delete_link(requested_users, deleting);
-        }
+        data->requested = FALSE;
+
+
         g_free(user_name);
         return;
     }
 
     icon_id = purple_imgstore_add_with_id(g_memdup(url_text, len), len,
                                           user_name);
-    g_hash_table_insert(icon_id_by_user,
-                        g_strdup(user_name),GINT_TO_POINTER(icon_id));
+    if(!data) {
+        data = g_new0(icon_data, 1);
+    }
+    data->icon_id = icon_id;
+    g_hash_table_insert(icon_data_by_user,
+                        g_strdup(user_name), data);
 
     const gchar *dirname = purple_prefs_get_string(OPT_ICON_DIR);
 
@@ -1057,21 +1096,26 @@
 request_icon(const char *user_name)
 {
     gchar *url = NULL;
-    PurpleUtilFetchUrlData *fetch_data = NULL;
 
     /* look local icon cache for the requested icon */
     gchar *filename = NULL;
     gchar *path = NULL;
-    gint icon_id;
+    icon_data *data = NULL;
+
+    data = (icon_data *)g_hash_table_lookup(icon_data_by_user, user_name);
+
+    if(!data) {
+        data = g_new0(icon_data, 1);
+        g_hash_table_insert(icon_data_by_user,
+                            g_strdup(user_name), data);
+    }
 
     /* if img has been registerd, just return */
-    icon_id = GPOINTER_TO_INT(g_hash_table_lookup(icon_id_by_user, user_name));
-    if(icon_id) {
+    if(data->icon_id)
         return;
-    }
 
+    /* check if saved file exists */
     filename = g_strdup_printf("%s.gif", user_name);
-
     path = g_build_filename(
         purple_prefs_get_string(OPT_ICON_DIR), filename, NULL);
 
@@ -1083,32 +1127,32 @@
     }
     /* build image from file, if file exists */
     if(g_file_test(path, G_FILE_TEST_EXISTS)) {
-        gchar *data = NULL;
+        gchar *imgdata = NULL;
         size_t len;
         GError *err = NULL;
 
-        if (!g_file_get_contents(path, &data, &len, &err)) {
+        if (!g_file_get_contents(path, &imgdata, &len, &err)) {
             twitter_debug("Error reading %s: %s\n",
                                path, err->message);
             g_error_free(err);
         }
 
-        icon_id = purple_imgstore_add_with_id(data, len, path);
-
-        g_hash_table_insert(icon_id_by_user, g_strdup(user_name), GINT_TO_POINTER(icon_id));
+        data->icon_id = purple_imgstore_add_with_id(imgdata, len, path);
         g_free(filename);
         g_free(path);
 
+        twitter_debug("icon data has been loaded from file\n");
+
         insert_requested_icon(user_name);
         return;
     }
 
     /* Return if user's icon has been requested already. */
-    if(g_list_find_custom(requested_users, user_name, (GCompareFunc)strcmp)) {
+    if(data->requested)
         return;
-    }
+    else
+        data->requested = TRUE;
 
-    requested_users = g_list_append(requested_users, g_strdup(user_name));
 
     /* Create the URL of the user's icon.
      * See http://twitter.g.hatena.ne.jp/ikko615/20080107/1199703400
@@ -1116,10 +1160,9 @@
     url = g_strdup_printf("http://img.twitty.jp/twitter/user/%s/m.gif",
                           user_name);
 
-    fetch_data = purple_util_fetch_url(url, TRUE, NULL, TRUE,
+    data->fetch_data = purple_util_fetch_url(url, TRUE, NULL, TRUE,
                                        got_icon_cb, g_strdup(user_name));
-    g_free(url);
-    requestings = g_list_append(requestings, fetch_data);
+    g_free(url); url = NULL;
 
     twitter_debug("request %s's icon\n", user_name);
 }
@@ -1127,11 +1170,18 @@
 static void
 mark_icon_for_user(GtkTextMark *mark, const gchar *user_name)
 {
-    GList *mark_list = g_hash_table_lookup(icon_mark_list_by_user, user_name);
+    icon_data *data = NULL;
+    data = (icon_data *)g_hash_table_lookup(icon_data_by_user, user_name);
 
+    if(!data) {
+        data = g_new0(icon_data, 1);
+        g_hash_table_insert(icon_data_by_user,
+                            g_strdup(user_name), data);
+    }
+
+    GList *mark_list = data->request_list;
     mark_list = g_list_append(mark_list, mark);
-    g_hash_table_replace(icon_mark_list_by_user,
-                         g_strdup(user_name), mark_list);
+    data->request_list = mark_list;
 }
 
 static void
@@ -1170,12 +1220,17 @@
     gtk_text_buffer_get_iter_at_line(text_buffer, &inserting_point,
         gtk_text_buffer_get_line_count(text_buffer) - 1);
 
-    icon_id = GPOINTER_TO_INT(g_hash_table_lookup(icon_id_by_user, user_name));
+    icon_data *data = NULL;
+    data = g_hash_table_lookup(icon_data_by_user, user_name);
+    if(data)
+        icon_id = data->icon_id;
+    else
+        icon_id = 0;
 
     /* If the user's icon has not been downloaded,
      * mark the message and request the icon. */
     if(!icon_id) {
-        twitter_debug("%s's icon has not been downloaded.\n", user_name);
+        twitter_debug("%s's icon is not in memory.\n", user_name);
         mark_icon_for_user(gtk_text_buffer_create_mark(
                                 text_buffer, NULL, &inserting_point, FALSE),
                            user_name);
@@ -1219,13 +1274,8 @@
     regp[USER_FIRST_LINE] = g_regex_new(P_USER_FIRST_LINE, 0, 0, NULL);
     regp[USER_FORMATTED]  = g_regex_new(P_USER_FORMATTED, G_REGEX_RAW, 0, NULL);
 
-    /* hash table for user's icons */
-    icon_id_by_user = g_hash_table_new_full(g_str_hash, g_str_equal,
-                                            g_free, NULL);
-
-    /* hash table for a list of marks that are alternative icons */
-    icon_mark_list_by_user = g_hash_table_new_full(g_str_hash, g_str_equal,
-                                                   g_free, NULL);
+    icon_data_by_user = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                              g_free, NULL);
 
     /* attach counter to the existing twitter window */
     if(purple_prefs_get_bool(OPT_COUNTER)) {
@@ -1235,11 +1285,29 @@
     return TRUE;
 }
 
+static void
+cancel_fetch_func(gpointer key, gpointer value, gpointer user_data)
+{
+    icon_data *data = (icon_data *)value;
+
+    if(!data)
+        return;
+
+    if(data->fetch_data) {
+        purple_util_fetch_url_cancel(data->fetch_data);
+        data->fetch_data = NULL;
+    }
+
+    if(data->request_list) {
+        twitter_debug("somehow, request_list != NULL\n");
+    }
+
+    return;
+}
+
 static gboolean
 unload_plugin(PurplePlugin *plugin)
 {
-    GList *list, *current;
-
     twitter_debug("called\n");
 
     /* disconnect from signal */
@@ -1271,45 +1339,14 @@
     g_regex_unref(regp[USER_FIRST_LINE]);
     g_regex_unref(regp[USER_FORMATTED]);
 
-    /* remove the hash table of marks that are alternative icons */
-    list = g_hash_table_get_values(icon_mark_list_by_user);
-    for(current = g_list_first(list); current;
-            current = g_list_next(current)) {
-        GList *mark_list = current->data;
-        GList *current_mark_list;
-
-        /* remove the marks in its GtkTextBuffers */
-        for(current_mark_list = g_list_first(mark_list); current_mark_list;
-                current_mark_list = g_list_next(current_mark_list)) {
-            GtkTextMark *current_mark = current_mark_list->data;
-            GtkTextBuffer *text_buffer = gtk_text_mark_get_buffer(
-                                             current_mark);
-
-            if(text_buffer) {
-                /* the mark will be freed in this function */
-                gtk_text_buffer_delete_mark(text_buffer, current_mark);
-            }
-        }
-
-        g_list_free(mark_list);
-    }
-    g_list_free(list);
-    g_hash_table_destroy(icon_mark_list_by_user);
+    /* remove mark list in each hash entry */
+    g_hash_table_foreach(icon_data_by_user, (GHFunc)remove_marks_func, NULL);
 
     /* cancel request that has not been finished yet */
-    for(list = g_list_first(requestings); list; list = g_list_next(list)) {
-        purple_util_fetch_url_cancel(list->data);
-    }
-    g_list_free(requestings);
-    requestings = NULL;
+    g_hash_table_foreach(icon_data_by_user, (GHFunc)cancel_fetch_func, NULL);
 
-    /* destroy hash table for icons */
-    g_hash_table_destroy(icon_id_by_user);
-    for(list = g_list_first(requested_users); list; list = g_list_next(list)) {
-        g_free(list->data);
-    }
-    g_list_free(requested_users);
-    requested_users = NULL;
+    /* destroy hash table for icon_data */
+    g_hash_table_destroy(icon_data_by_user); //XXX all memory freed? --yaz
 
     /* detach from twitter window */
     detach_from_window();
--- a/pidgin-twitter.h	Mon Jun 30 11:02:01 2008 +0900
+++ b/pidgin-twitter.h	Tue Jul 01 18:20:48 2008 +0900
@@ -99,5 +99,7 @@
 static void counter_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data);
 static PurplePluginPrefFrame *get_plugin_pref_frame(PurplePlugin *plugin);
 static void init_plugin(PurplePlugin *plugin);
+static void remove_marks_func(gpointer key, gpointer value, gpointer user_data);
+static void cancel_fetch_func(gpointer key, gpointer value, gpointer user_data);
 
 #endif