diff icon.c @ 254:c2620a99622b

- divided the source file into several parts.
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Sat, 22 Nov 2008 18:01:18 +0900
parents
children 2145f975ef69
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/icon.c	Sat Nov 22 18:01:18 2008 +0900
@@ -0,0 +1,677 @@
+#include "pidgin-twitter.h"
+#include "icon.h"
+
+extern GHashTable *icon_hash[];
+extern GRegex *regp[];
+
+/* prototypes */
+static void insert_icon_at_mark(GtkTextMark *requested_mark, gpointer user_data);
+static void insert_requested_icon(const gchar *user_name, gint service);
+static void got_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message);
+static GdkPixbuf *make_scaled_pixbuf(const gchar *url_text, gsize len);
+static void got_page_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message);
+
+/* functions */
+static void
+insert_icon_at_mark(GtkTextMark *requested_mark, gpointer user_data)
+{
+    got_icon_data *gotdata = (got_icon_data *)user_data;
+
+    gchar *user_name = gotdata->user_name;
+    gint service = gotdata->service;
+
+    GList *win_list;
+    GtkIMHtml *target_imhtml = NULL;
+    GtkTextBuffer *target_buffer = NULL;
+    GtkTextIter insertion_point;
+    icon_data *data = NULL;
+    GHashTable *hash = NULL;
+
+    twitter_debug("called: service = %d\n", service);
+
+    /* find the conversation that contains the mark  */
+    for(win_list = pidgin_conv_windows_get_list(); win_list;
+            win_list = win_list->next) {
+        PidginWindow *win = win_list->data;
+        GList *conv_list;
+
+        for(conv_list = pidgin_conv_window_get_gtkconvs(win); conv_list;
+                conv_list = conv_list->next) {
+            PidginConversation *conv = conv_list->data;
+            PurpleConversation *purple_conv = conv->active_conv;
+
+            gint service = get_service_type(purple_conv);
+
+            if(service != unknown_service) {
+                GtkIMHtml *current_imhtml = GTK_IMHTML(conv->imhtml);
+                GtkTextBuffer *current_buffer = gtk_text_view_get_buffer(
+                     GTK_TEXT_VIEW(current_imhtml));
+
+                if(current_buffer == gtk_text_mark_get_buffer(requested_mark)) {
+                     target_imhtml = current_imhtml;
+                     target_buffer = current_buffer;
+                     break;
+                }
+            }
+        }
+    }
+
+    if(!(target_imhtml && target_buffer)) {
+        return;
+    }
+
+    /* insert icon to the mark */
+    gtk_text_buffer_get_iter_at_mark(target_buffer,
+                                     &insertion_point, requested_mark);
+
+    /* insert icon */
+    switch(service) {
+    case twitter_service:
+        hash = icon_hash[twitter_service];
+        break;
+    case wassr_service:
+        hash = icon_hash[wassr_service];
+        break;
+    case identica_service:
+        hash = icon_hash[identica_service];
+        break;
+    case jisko_service:
+        hash = icon_hash[jisko_service];
+        break;
+    default:
+        twitter_debug("unknown service\n");
+    }
+
+    if(hash)
+        data = (icon_data *)g_hash_table_lookup(hash, user_name);
+
+
+    /* in this function, we put an icon for pending marks. we should
+     * not invalidate the icon here, otherwise it may result in
+     * thrashing. --yaz */
+
+    if(!data || !data->pixbuf) {
+        return;
+    }
+
+    /* insert icon actually */
+    if(purple_prefs_get_bool(OPT_SHOW_ICON)) {
+        gtk_text_buffer_insert_pixbuf(target_buffer,
+                                      &insertion_point,
+                                      data->pixbuf);
+        data->use_count++;
+    }
+    gtk_text_buffer_delete_mark(target_buffer, requested_mark);
+    requested_mark = NULL;
+}
+
+static void
+insert_requested_icon(const gchar *user_name, gint service)
+{
+    icon_data *data = NULL;
+    GList *mark_list = NULL;
+    GHashTable *hash = NULL;
+
+    twitter_debug("called\n");
+
+    switch(service) {
+    case twitter_service:
+        hash = icon_hash[twitter_service];
+        break;
+    case wassr_service:
+        hash = icon_hash[wassr_service];
+        break;
+    case identica_service:
+        hash = icon_hash[identica_service];
+        break;
+    case jisko_service:
+        hash = icon_hash[jisko_service];
+        break;
+    default:
+        twitter_debug("unknown service\n");
+        break;
+    }
+
+    if(hash)
+        data = (icon_data *)g_hash_table_lookup(hash, user_name);
+
+    if(!data)
+        return;
+
+    mark_list = data->request_list;
+
+    got_icon_data *gotdata = g_new0(got_icon_data, 1);
+    gotdata->user_name = g_strdup(user_name);
+    gotdata->service = service;
+
+    twitter_debug("about to insert icon for pending requests\n");
+
+    if(mark_list) {
+        g_list_foreach(mark_list, (GFunc) insert_icon_at_mark, gotdata);
+        mark_list = g_list_remove_all(mark_list, NULL);
+        g_list_free(mark_list);
+        data->request_list = NULL;
+    }
+
+    g_free(gotdata->user_name);
+    g_free(gotdata);
+}
+
+/* this function will be called when profile page has been retrieved */
+static void
+got_page_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
+            const gchar *url_text, gsize len, const gchar *error_message)
+{
+    got_icon_data *gotdata = (got_icon_data *)user_data;
+    gchar *user_name = gotdata->user_name;
+    gint service = gotdata->service;
+    GMatchInfo *match_info = NULL;
+    icon_data *data = NULL;
+    gchar *url = NULL;
+    gint regp_id = -1;
+
+    if(service == twitter_service) {
+        data = (icon_data *)g_hash_table_lookup(
+            icon_hash[twitter_service], user_name);
+        regp_id = IMAGE_TWITTER;
+    }
+    else if(service == wassr_service) {
+        data = (icon_data *)g_hash_table_lookup(
+            icon_hash[wassr_service], user_name);
+        regp_id = IMAGE_WASSR;
+    }
+    else if(service == identica_service) {
+        data = (icon_data *)g_hash_table_lookup(
+            icon_hash[identica_service], user_name);
+        regp_id = IMAGE_IDENTICA;
+    }
+    else if(service == jisko_service) {
+        data = (icon_data *)g_hash_table_lookup(
+            icon_hash[jisko_service], user_name);
+        regp_id = IMAGE_JISKO;
+    }
+
+    if(!url_text) {
+        if(data) {
+            data->requested = FALSE;
+            data->fetch_data = NULL;
+        }
+        g_free(gotdata->user_name);
+        g_free(gotdata);
+        return;
+    }
+
+    /* setup image url */ /* xxx need simplify --yaz */
+    g_regex_match(regp[regp_id], url_text, 0, &match_info);
+    if(!g_match_info_matches(match_info)) {
+        g_match_info_free(match_info);
+
+        if(service == twitter_service) {
+            twitter_debug("fall back to twitter default icon\n");
+            url = g_strdup(TWITTER_DEFAULT_ICON_URL);
+        }
+        else if(service == jisko_service) {
+            twitter_debug("fall back to jisko default icon\n");
+            url = g_strdup(JISKO_DEFAULT_ICON_URL);
+        }
+        else {
+            twitter_debug("no image url found\n");
+            if(data) {
+                data->requested = FALSE;
+                data->fetch_data = NULL;
+            }
+            g_free(gotdata->user_name);
+            g_free(gotdata);
+            return;
+        }
+    }
+    else {
+        url = g_match_info_fetch(match_info, 1);
+        g_match_info_free(match_info);
+    }
+
+    /* find out basename */
+    gchar *slash = strrchr(url, '/');
+    *slash = '\0';
+
+    gchar *lower = g_ascii_strdown(slash+1, -1);
+
+    if(strstr(lower, ".png"))
+        data->img_type = "png";
+    else if(strstr(lower, ".gif"))
+        data->img_type = "gif";
+    else if(strstr(lower, ".jpg") || strstr(lower, ".jpeg"))
+        data->img_type = "jpg";
+
+    g_free(lower);
+
+    gchar *tmp;
+    /* url encode basename. twitter needs this. */
+    if(service == twitter_service)
+        tmp = g_strdup_printf("%s/%s", url,
+                              purple_url_encode(slash+1));
+    else if(service == wassr_service) {
+        gchar *tmp0 = NULL;
+        tmp0 = g_regex_replace(regp[SIZE_128_WASSR], slash+1,
+                                     -1, 0, ".64.", 0, NULL);
+        tmp = g_strdup_printf("http://wassr.jp%s/%s", url,
+                              tmp0 ? tmp0 : slash+1);
+        g_free(tmp0);
+    }
+    else {
+        tmp = g_strdup_printf("%s/%s", url, slash+1);
+    }
+
+    g_free(url);
+    url = tmp;
+
+    /* if requesting icon url is the same as old, return. */
+    if(url && data->icon_url && !strcmp(data->icon_url, url)) {
+        twitter_debug("old url = %s new url = %s\n", data->icon_url, url);
+        data->requested = FALSE;
+        data->fetch_data = NULL;
+        g_free(url);
+        return;
+    }
+
+    if(data && data->pixbuf) {
+        gdk_pixbuf_unref(data->pixbuf);
+        data->pixbuf = NULL;
+    }
+
+    g_free(data->icon_url);
+    data->icon_url = g_strdup(url);
+
+    data->use_count = 0;
+    data->mtime = time(NULL); /* xxx is there a better way? */
+
+    twitter_debug("requested url=%s\n", url);
+
+    /* request fetch image */
+    if(url) {
+        /* reuse gotdata. just pass given one */
+        /* gotdata will be released in got_icon_cb */
+        data->fetch_data = purple_util_fetch_url(url,
+                                                 TRUE, NULL, TRUE,
+                                                 got_icon_cb, gotdata);
+        twitter_debug("request %s's icon\n", user_name);
+        g_free(url);
+    }
+}
+
+static GdkPixbuf *
+make_scaled_pixbuf(const gchar *url_text, gsize len)
+{
+    /* make pixbuf */
+    GdkPixbufLoader *loader;
+    GdkPixbuf *src = NULL, *dest = NULL;
+    gint size;
+
+    g_return_val_if_fail(url_text != NULL, NULL);
+    g_return_val_if_fail(len > 0, NULL);
+
+    loader = gdk_pixbuf_loader_new();
+    gdk_pixbuf_loader_write(loader, (guchar *)url_text, len, NULL);
+    gdk_pixbuf_loader_close(loader, NULL);
+
+    src = gdk_pixbuf_loader_get_pixbuf(loader);
+    if(!src)
+        return NULL;
+
+    size = purple_prefs_get_int(OPT_ICON_SIZE);
+    if(size == 0)
+        size = DEFAULT_ICON_SIZE;
+
+    dest = gdk_pixbuf_scale_simple(src, size, size, GDK_INTERP_HYPER);
+    gdk_pixbuf_unref(src);
+
+    return dest;
+}
+
+static gchar *ext_list[] = {
+    "png",
+    "gif",
+    "jpg",
+    NULL
+};
+
+/* this function will be called when requested icon has been retrieved */
+static void
+got_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
+            const gchar *url_text, gsize len, const gchar *error_message)
+{
+    got_icon_data *gotdata = (got_icon_data *)user_data;
+    gchar *user_name = gotdata->user_name;
+    gint service = gotdata->service;
+
+    icon_data *data = NULL;
+    GHashTable *hash = NULL;
+    GdkPixbuf *pixbuf = NULL;
+    const gchar *dirname = NULL;
+
+    twitter_debug("called: service = %d\n", service);
+
+    switch(service) {
+    case twitter_service:
+        hash = icon_hash[twitter_service];
+        break;
+    case wassr_service:
+        hash = icon_hash[wassr_service];
+        break;
+    case identica_service:
+        hash = icon_hash[identica_service];
+        break;
+    case jisko_service:
+        hash = icon_hash[jisko_service];
+        break;
+    default:
+        twitter_debug("unknown service\n");
+    }
+
+    if(hash)
+        data = (icon_data *)g_hash_table_lookup(hash, user_name);
+
+    /* return if download failed */
+    if(!url_text) {
+        twitter_debug("downloading %s's icon failed : %s\n",
+                      user_name, error_message);
+        if(data)
+            data->requested = FALSE;
+
+        goto fin_got_icon_cb;
+    }
+
+    if(data) {
+        /* remove download request */
+        data->requested = FALSE;
+        data->fetch_data = NULL;
+
+        /* return if user's icon had been downloaded */
+        if(data->pixbuf) {
+            twitter_debug("%s's icon has already been downloaded\n",
+                          user_name);
+
+            goto fin_got_icon_cb;
+        }
+    }
+
+    pixbuf = make_scaled_pixbuf(url_text, len);
+
+    if(!pixbuf)
+        goto fin_got_icon_cb;
+
+
+    if(!data) {
+        twitter_debug("allocate icon_data (shouldn't be called)\n");
+        data = g_new0(icon_data, 1);
+    }
+
+    data->pixbuf = pixbuf;
+
+    twitter_debug("new icon pixbuf = %p size = %d\n",
+                  pixbuf,
+                  gdk_pixbuf_get_rowstride(pixbuf) *
+                  gdk_pixbuf_get_height(pixbuf));
+
+    if(hash)
+        g_hash_table_insert(hash, g_strdup(user_name), data);
+
+    dirname = purple_prefs_get_string(OPT_ICON_DIR);
+
+    /* store retrieved image to a file in icon dir */
+    if(ensure_path_exists(dirname)) {
+        gchar *filename = NULL;
+        gchar *path = NULL;
+        const gchar *suffix = NULL;
+        gchar **extp;
+
+        switch(service) {
+        case twitter_service:
+            suffix = "twitter";
+            break;
+        case wassr_service:
+            suffix = "wassr";
+            break;
+        case identica_service:
+            suffix = "identica";
+            break;
+        case jisko_service:
+            suffix = "jisko";
+            break;
+        default:
+            twitter_debug("unknown service\n");
+            break;
+        }
+
+        /* remove old file first */
+        for(extp = ext_list; *extp; extp++) {
+            filename = g_strdup_printf("%s_%s.%s",
+                                       user_name, suffix, *extp);
+            path = g_build_filename(dirname, filename, NULL);
+            g_remove(path);
+
+            g_free(filename);
+            g_free(path);
+        }
+
+        /* setup path */
+        filename = g_strdup_printf("%s_%s.%s",
+                                   user_name, suffix, data->img_type);
+
+        path = g_build_filename(dirname, filename, NULL);
+        g_free(filename); filename = NULL;
+
+        g_file_set_contents(path, url_text, len, NULL);
+        g_free(path); path = NULL;
+
+        data->mtime = time(NULL);
+    }
+
+    twitter_debug("Downloading %s's icon has been complete.\n",
+        user_name);
+
+    /* Insert the icon to messages that had been received. */
+    insert_requested_icon(user_name, service);
+
+fin_got_icon_cb:
+    g_free(gotdata->user_name);
+    g_free(gotdata);
+}
+
+void
+request_icon(const char *user_name, gint service, gboolean renew)
+{
+    gchar *url = NULL;
+
+    /* look local icon cache for the requested icon */
+    gchar *path = NULL;
+    icon_data *data = NULL;
+    GHashTable *hash = NULL;
+    const gchar *suffix = NULL;
+
+    switch(service) {
+    case twitter_service:
+        hash = icon_hash[twitter_service];
+        suffix = "twitter";
+        break;
+    case wassr_service:
+        hash = icon_hash[wassr_service];
+        suffix = "wassr";
+        break;
+    case identica_service:
+        suffix = "identica";
+        hash = icon_hash[identica_service];
+        break;
+    case jisko_service:
+        suffix = "jisko";
+        hash = icon_hash[jisko_service];
+        break;
+    default:
+        twitter_debug("unknown service\n");
+        break;
+    }
+
+    if(!hash)
+        return;
+
+    /* since this function is called after mark_icon_for_user(), data
+     * must exist here. */
+    data = (icon_data *)g_hash_table_lookup(hash, user_name);
+
+    /* if img has been registerd, just return */
+    if(data && data->pixbuf && !renew)
+        return;
+
+    /* check if saved file exists */
+    if(suffix && !renew) {
+        gchar *filename = NULL;
+        gchar **extp;
+
+        for(extp = ext_list; *extp; extp++) {
+            filename = g_strdup_printf("%s_%s.%s", user_name, suffix, *extp);
+            path = g_build_filename(purple_prefs_get_string(OPT_ICON_DIR),
+                                    filename, NULL);
+            g_free(filename);
+
+            twitter_debug("path = %s\n", path);
+
+            /* build image from file, if file exists */
+            if(g_file_test(path, G_FILE_TEST_EXISTS)) {
+                gchar *imgdata = NULL;
+                size_t len;
+                GError *err = NULL;
+                GdkPixbuf *pixbuf = NULL;
+                struct stat buf;
+
+                if (!g_file_get_contents(path, &imgdata, &len, &err)) {
+                    twitter_debug("Error reading %s: %s\n",
+                                  path, err->message);
+                    g_error_free(err);
+                }
+
+                if(stat(path, &buf))
+                    data->mtime = buf.st_mtime;
+
+                pixbuf = make_scaled_pixbuf(imgdata, len);
+                g_free(imgdata);
+
+                if(pixbuf) {
+                    data->pixbuf = pixbuf;
+
+                    twitter_debug("new icon pixbuf = %p size = %d\n",
+                                  pixbuf,
+                                  gdk_pixbuf_get_rowstride(pixbuf) *
+                                  gdk_pixbuf_get_height(pixbuf));
+
+                    data->img_type = *extp;
+
+                    twitter_debug("icon data has been loaded from file\n");
+                    insert_requested_icon(user_name, service);
+                }
+
+                g_free(path);
+                return;
+            }
+
+            g_free(path);
+
+        } /* for */
+    } /* suffix */
+
+    /* Return if user's icon has been requested already. */
+    if(data->requested)
+        return;
+    else
+        data->requested = TRUE;
+
+    /* Create the URL for an user's icon. */
+    switch(service) {
+    case twitter_service:
+        url = g_strdup_printf("http://twitter.com/%s", user_name);
+        break;
+    case wassr_service:
+        url = g_strdup_printf("http://wassr.jp/user/%s", user_name);
+        break;
+    case identica_service:
+        url = g_strdup_printf("http://identi.ca/%s", user_name);
+        break;
+    case jisko_service:
+        url = g_strdup_printf("http://jisko.net/%s", user_name);
+        break;
+    default:
+        twitter_debug("unknown service\n");
+        break;
+    }
+
+    if(url) {
+        got_icon_data *gotdata = g_new0(got_icon_data, 1);
+        gotdata->user_name = g_strdup(user_name);
+        gotdata->service = service;
+
+        /* gotdata will be released in got_icon_cb */
+        if(service == twitter_service ||
+           service == wassr_service ||
+           service == identica_service ||
+           service == jisko_service) {
+            data->fetch_data = purple_util_fetch_url(url, TRUE, NULL, TRUE,
+                                                     got_page_cb, gotdata);
+        }
+        else {
+            data->fetch_data = purple_util_fetch_url(url, TRUE, NULL, TRUE,
+                                                     got_icon_cb, gotdata);
+        }
+        g_free(url); url = NULL;
+
+        twitter_debug("request %s's icon\n", user_name);
+    }
+}
+
+void
+mark_icon_for_user(GtkTextMark *mark, const gchar *user_name, gint service)
+{
+    icon_data *data = NULL;
+    GHashTable *hash = NULL;
+
+    twitter_debug("called\n");
+
+    switch(service) {
+    case twitter_service:
+        hash = icon_hash[twitter_service];
+        break;
+    case wassr_service:
+        hash = icon_hash[wassr_service];
+        break;
+    case identica_service:
+        hash = icon_hash[identica_service];
+        break;
+    case jisko_service:
+        hash = icon_hash[jisko_service];
+        break;
+    default:
+        twitter_debug("unknown service\n");
+        break;
+    }
+
+    if(hash)
+        data = (icon_data *)g_hash_table_lookup(hash, user_name);
+
+    /* proper place to allocate icon_data */
+    if(!data) {
+        data = g_new0(icon_data, 1);
+        g_hash_table_insert(hash, g_strdup(user_name), data);
+    }
+
+    data->request_list = g_list_prepend(data->request_list, mark);
+}
+
+void
+invalidate_icon_data_func(gpointer key, gpointer value, gpointer user_data)
+{
+    icon_data *data = (icon_data *)value;
+
+    g_return_if_fail(data != NULL);
+
+    g_object_unref(data->pixbuf);
+    data->pixbuf = NULL;
+}
+