1/*
2 * This file Copyright (C) Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2. Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: favicon.c 12655 2011-08-08 17:10:55Z jordan $
11 */
12
13#include <glib/gstdio.h> /* g_remove() */
14#include <gtk/gtk.h>
15
16#include <libtransmission/transmission.h>
17#include <libtransmission/web.h> /* tr_webRun() */
18
19#include "favicon.h"
20#include "util.h" /* gtr_get_host_from_url() */
21
22#define IMAGE_TYPES 4
23static const char * image_types[IMAGE_TYPES] = { "ico", "png", "gif", "jpg" };
24
25struct favicon_data
26{
27    tr_session * session;
28    GFunc func;
29    gpointer data;
30    char * host;
31    char * contents;
32    size_t len;
33    int type;
34};
35
36static char*
37get_url( const char * host, int image_type )
38{
39    return g_strdup_printf( "http://%s/favicon.%s", host, image_types[image_type] );
40}
41
42static char*
43favicon_get_cache_dir( void )
44{
45    static char * dir = NULL;
46
47    if( dir == NULL )
48    {
49        dir = g_build_filename( g_get_user_cache_dir(),
50                                "transmission",
51                                "favicons",
52                                NULL );
53        g_mkdir_with_parents( dir, 0777 );
54    }
55
56    return dir;
57}
58
59static char*
60favicon_get_cache_filename( const char * host )
61{
62    return g_build_filename( favicon_get_cache_dir(), host, NULL );
63}
64
65static void
66favicon_save_to_cache( const char * host, const void * data, size_t len )
67{
68    char * filename = favicon_get_cache_filename( host );
69    g_file_set_contents( filename, data, len, NULL );
70    g_free( filename );
71}
72
73static GdkPixbuf*
74favicon_load_from_cache( const char * host )
75{
76    char * filename = favicon_get_cache_filename( host );
77    GdkPixbuf * pixbuf = gdk_pixbuf_new_from_file_at_size( filename, 16, 16, NULL );
78    if( pixbuf == NULL ) /* bad file */
79        g_remove( filename );
80    g_free( filename );
81    return pixbuf;
82}
83
84static void favicon_web_done_cb( tr_session*, bool, bool, long, const void*, size_t, void* );
85
86static gboolean
87favicon_web_done_idle_cb( gpointer vfav )
88{
89    GdkPixbuf * pixbuf = NULL;
90    gboolean finished = FALSE;
91    struct favicon_data * fav = vfav;
92
93    if( fav->len > 0 ) /* we got something... try to make a pixbuf from it */
94    {
95        favicon_save_to_cache( fav->host, fav->contents, fav->len );
96        pixbuf = favicon_load_from_cache( fav->host );
97        finished = pixbuf != NULL;
98    }
99
100    if( !finished ) /* no pixbuf yet... */
101    {
102        if( ++fav->type == IMAGE_TYPES ) /* failure */
103        {
104            finished = TRUE;
105        }
106        else /* keep trying */
107        {
108            char * url = get_url( fav->host, fav->type );
109
110            g_free( fav->contents );
111            fav->contents = NULL;
112            fav->len = 0;
113
114            tr_webRun( fav->session, url, NULL, NULL, favicon_web_done_cb, fav );
115            g_free( url );
116        }
117    }
118
119    if( finished )
120    {
121        fav->func( pixbuf, fav->data );
122        g_free( fav->host );
123        g_free( fav->contents );
124        g_free( fav );
125    }
126
127    return FALSE;
128}
129
130static void
131favicon_web_done_cb( tr_session    * session UNUSED,
132                     bool            did_connect UNUSED,
133                     bool            did_timeout UNUSED,
134                     long            code UNUSED,
135                     const void    * data,
136                     size_t          len,
137                     void          * vfav )
138{
139    struct favicon_data * fav = vfav;
140    fav->contents = g_memdup( data, len );
141    fav->len = len;
142
143    gdk_threads_add_idle( favicon_web_done_idle_cb, fav );
144}
145
146void
147gtr_get_favicon( tr_session  * session,
148                 const char  * host,
149                 GFunc         pixbuf_ready_func,
150                 gpointer      pixbuf_ready_func_data )
151{
152    GdkPixbuf * pixbuf = favicon_load_from_cache( host );
153
154    if( pixbuf != NULL )
155    {
156        pixbuf_ready_func( pixbuf, pixbuf_ready_func_data );
157    }
158    else
159    {
160        struct favicon_data * data;
161        char * url = get_url( host, 0 );
162
163        data = g_new( struct favicon_data, 1 );
164        data->session = session;
165        data->func = pixbuf_ready_func;
166        data->data = pixbuf_ready_func_data;
167        data->host = g_strdup( host );
168        data->type = 0;
169
170        tr_webRun( session, url, NULL, NULL, favicon_web_done_cb, data );
171        g_free( url );
172    }
173}
174
175void
176gtr_get_favicon_from_url( tr_session  * session,
177                          const char  * url,
178                          GFunc         pixbuf_ready_func,
179                          gpointer      pixbuf_ready_func_data )
180{
181    char host[1024];
182    gtr_get_host_from_url( host, sizeof( host ), url );
183    gtr_get_favicon( session, host, pixbuf_ready_func, pixbuf_ready_func_data );
184}
185