Bump version to 0.15.4.102
[gmpc-coveramazon.git] / src / plugin.c
blob68fd6a56c56f367f17590cef35d6b6a7aaa6a9c1
1 #include <stdio.h>
2 #include <string.h>
3 #include <gtk/gtk.h>
4 #include <libxml/parser.h>
5 #include <libxml/tree.h>
6 #include <libmpd/debug_printf.h>
7 #include <gmpc/plugin.h>
8 #include <gmpc/gmpc_easy_download.h>
9 #include <config.h>
10 #define AMAZONKEY "14TC04B24356BPHXW1R2"
12 static void init();
13 static char * host = "http://ecs.amazonaws.%s/onca/xml?Service=AWSECommerceService&Operation=ItemSearch&SearchIndex=Music&ResponseGroup=Images,EditorialReview&SubscriptionId=%s&Artist=%s&%s=%s";
14 static char* search_types[] = {"Title", "Keywords"};
15 static void amazon_cover_art_pref_construct(GtkWidget *container);
16 static void amazon_cover_art_pref_destroy(GtkWidget *container);
17 static int fetch_cover_art(mpd_Song *song,int type, char **url);
18 static int fetch_cover_priority();
20 static int shrink_string(gchar *string, int start, int end);
22 static int fetch_metadata(mpd_Song *song,MetaDataType type, char **path);
24 static void amazon_set_enabled(int enabled);
25 static int amazon_get_enabled();
27 #define ENDPOINTS 6
28 static char *endpoints[ENDPOINTS][2] =
30 {"com", "United States"},
31 {"co.uk", "United Kingdom"},
32 {"jp", "Japan"},
33 {"fr", "France"},
34 {"ca", "Canada"},
35 {"de", "Germany"}
38 static GtkWidget *wp_pref_vbox = NULL;
39 gmpcPrefPlugin cam_pref = {
40 amazon_cover_art_pref_construct,
41 amazon_cover_art_pref_destroy
44 typedef struct amazon_song_info {
45 char *image_big;
46 char *image_medium;
47 char *image_small;
48 char *album_info;
50 }amazon_song_info;
52 gmpcMetaDataPlugin cam_cover = {
53 fetch_cover_priority,
54 fetch_metadata
57 int plugin_api_version = PLUGIN_API_VERSION;
58 gmpcPlugin plugin = {
59 "Amazon Cover Fetcher",
60 {PLUGIN_MAJOR_VERSION,PLUGIN_MINOR_VERSION,PLUGIN_MICRO_VERSION},
61 GMPC_PLUGIN_META_DATA,
63 NULL, /* path */
64 init, /* init */
65 NULL, /* Destroy */
66 NULL, /* browser */
67 NULL, /* status changed */
68 NULL, /* connection changed */
69 &cam_pref, /* preferences */
70 &cam_cover, /** Metadata */
71 amazon_get_enabled,
72 amazon_set_enabled
75 /* Convert string to the wonderful % notation for url*/
76 static char * __cover_art_process_string(const gchar *string)
78 #define ACCEPTABLE(a) (((a) >= 'a' && (a) <= 'z') || ((a) >= 'A' && (a) <= 'Z') || ((a) >= '0' && (a) <= '9'))
80 const gchar hex[16] = "0123456789ABCDEF";
81 const gchar *p;
82 gchar *q;
83 gchar *result;
84 int c;
85 gint unacceptable = 0;
86 const gchar *tmp_p;
87 gchar *new_string;
88 int depth = 0;
89 int len;
90 int i = 0;
92 len = strlen(string);
94 new_string = g_malloc(len + 1);
96 /* Get count of chars that will need to be converted to %
97 and remove ([{}]) and everything between */
98 for (p = string; *p != '\0'; p++) {
99 c = (guchar) *p;
101 if(c == '(' || c == '[' || c == '{') {
102 depth++;
103 } else if(c == ')' || c == ']' || c == '}') {
104 depth--;
105 if(depth < 0)
106 depth = 0;
107 } else if(depth == 0) {
108 if (!ACCEPTABLE (c))
109 unacceptable++;
111 new_string[i] = c;
113 i++;
117 new_string[i] = '\0';
119 len = strlen(new_string);
121 /* remove double spaces from the string because removing ([{}])
122 tends to create those */
123 for(p = new_string + 1; *p != '\0'; p++) {
124 c = (guchar) *p;
125 if(c == ' ') {
126 tmp_p = p - 1;
127 if( *tmp_p == ' ') {
128 len = shrink_string(new_string, p - new_string, len);
129 p--;
134 /* make sure first char isn't a space */
135 if(new_string[0] == ' ')
136 len = shrink_string(new_string, 0, len);
138 /* make sure there isn't a trailing space*/
139 if(new_string[len - 1] == ' ')
140 len--;
142 new_string[len] = '\0';
144 result = g_malloc (len + unacceptable * 2 + 1);
146 /*time to create the escaped string*/
147 for (q = result, p = new_string; *p != '\0'; p++)
149 c = (guchar) *p;
151 if (!ACCEPTABLE (c)) {
152 *q++ = '%'; /* means hex coming */
153 *q++ = hex[c >> 4];
154 *q++ = hex[c & 15];
156 else
157 *q++ = *p;
160 *q = '\0';
162 g_free(new_string);
164 return result;
168 * Get Set enabled
170 static int amazon_get_enabled()
172 return cfg_get_single_value_as_int_with_default(config, "cover-amazon", "enable", TRUE);
174 static void amazon_set_enabled(int enabled)
176 cfg_set_single_value_as_int(config, "cover-amazon", "enable", enabled);
179 static void init()
181 char *file = gmpc_get_covers_path(NULL);
182 if(!g_file_test(file,G_FILE_TEST_EXISTS))
184 g_mkdir(file, 0755);
186 g_free(file);
189 static int fetch_cover_priority(){
190 return cfg_get_single_value_as_int_with_default(config, "cover-amazon", "priority", 80);
194 static int fetch_metadata(mpd_Song *song,MetaDataType type, char **path)
196 int j=2;
197 gchar *url = NULL;
198 gchar *filename;
200 if(song->artist == NULL || song->album == NULL)
202 return META_DATA_UNAVAILABLE;
204 if(type != META_ALBUM_ART && type != META_ALBUM_TXT) {
205 return META_DATA_UNAVAILABLE;
207 /* Always fetch it. */
208 fetch_cover_art(song,type, &url);
209 if(url){
210 *path = url;
211 return META_DATA_AVAILABLE;
214 g_free(url);
215 return META_DATA_UNAVAILABLE;
218 static amazon_song_info * amazon_song_info_new()
220 amazon_song_info *asi = g_malloc(sizeof(amazon_song_info));
221 asi->image_big = NULL;
222 asi->image_medium = NULL;
223 asi->image_small = NULL;
224 asi->album_info = NULL;
225 return asi;
227 static void amazon_song_info_free(amazon_song_info *asi)
229 if(asi->image_big != NULL) g_free(asi->image_big);
230 if(asi->image_medium != NULL) g_free(asi->image_medium);
231 if(asi->image_small != NULL) g_free(asi->image_small);
232 if(asi->album_info != NULL) g_free(asi->album_info);
233 g_free(asi);
234 return;
237 static xmlNodePtr get_first_node_by_name(xmlNodePtr xml, gchar *name) {
238 if(xml) {
239 xmlNodePtr c = xml->xmlChildrenNode;
240 for(;c;c=c->next) {
241 if(xmlStrEqual(c->name, (xmlChar *) name))
242 return c;
245 return NULL;
248 static amazon_song_info *__cover_art_xml_get_image(char *data,int size)
250 xmlDocPtr doc = xmlParseMemory(data,size);
251 if(doc)
253 xmlNodePtr root = xmlDocGetRootElement(doc);
254 xmlNodePtr cur = get_first_node_by_name(root, "Items");
255 amazon_song_info *asi = NULL;
256 if(cur) {
257 cur = get_first_node_by_name(cur, "Item");
258 if(cur) {
259 xmlNodePtr child = NULL;
260 asi = amazon_song_info_new();
261 if(child = get_first_node_by_name(cur, "LargeImage")) {
262 xmlChar *temp = xmlNodeGetContent(get_first_node_by_name(child, "URL"));
263 /* copy it, so we can free it, and don't need xmlFree */
264 asi->image_big = g_strdup((char *)temp);
265 xmlFree(temp);
267 if(child = get_first_node_by_name(cur, "MediumImage")){
268 xmlChar *temp = xmlNodeGetContent(get_first_node_by_name(child, "URL"));
269 asi->image_medium = g_strdup((char *)temp);
270 xmlFree(temp);
272 if(child = get_first_node_by_name(cur, "SmallImage")){
273 xmlChar *temp = xmlNodeGetContent(get_first_node_by_name(child, "URL"));
274 asi->image_small = g_strdup((char *)temp);
275 xmlFree(temp);
278 if (child = get_first_node_by_name(cur, "EditorialReviews")) {
279 child = get_first_node_by_name(child, "EditorialReview");
280 if(child) {
281 xmlChar *temp = xmlNodeGetContent(get_first_node_by_name(child, "Content")); /* ugy, lazy */
282 asi->album_info = g_strdup((char *)temp);
283 xmlFree(temp);
288 xmlFreeDoc(doc);
289 return asi;
291 xmlCleanupParser();
292 return NULL;
296 static int __fetch_metadata_amazon(char *stype, char *nartist, char *nalbum,int type, char **url)
299 gmpc_easy_download_struct data= {NULL, 0,-1, NULL, NULL};
300 int found = 0;
301 char furl[1024];
302 int endpoint = cfg_get_single_value_as_int_with_default(config, "cover-amazon", "location", 0);
303 char *endp = endpoints[endpoint][0];
305 debug_printf(DEBUG_INFO, "search-type: %s\n", stype);
306 snprintf(furl,1024, host,endp, AMAZONKEY, nartist, stype, nalbum);
307 if(gmpc_easy_download(furl, &data))
309 amazon_song_info *asi = __cover_art_xml_get_image(data.data, data.size);
310 gmpc_easy_download_clean(&data);
311 if(asi)
313 if(type&META_ALBUM_ART)
315 debug_printf(DEBUG_INFO, "Trying to fetch album art");
316 gmpc_easy_download(asi->image_big, &data);
317 if(data.size <= 900){
318 gmpc_easy_download_clean(&data);
319 gmpc_easy_download(asi->image_medium, &data);
320 if(data.size <= 900){
321 gmpc_easy_download_clean(&data);
322 gmpc_easy_download(asi->image_small, &data);
323 if(data.size <= 900)
325 gmpc_easy_download_clean(&data);
329 if(data.size){
330 int i =0;
331 FILE *fp = NULL;
332 gchar *imgpath = NULL;
333 gchar *filename = g_strdup_printf("%s-%s.jpg",nartist,nalbum);
334 imgpath = gmpc_get_covers_path(filename);
335 g_free(filename);
336 fp = fopen(imgpath, "wb");
337 if(fp)
339 fwrite(data.data, sizeof(char), data.size,fp);
340 *url = g_strdup(imgpath);
341 found = 1;
342 fclose(fp);
344 g_free(imgpath);
346 gmpc_easy_download_clean(&data);
350 else if(type&META_ALBUM_TXT)
352 debug_printf(DEBUG_INFO, "Trying to fetch album txt");
353 if(asi->album_info)
355 FILE *fp;
356 gchar *filename, *imgpath;
357 filename = g_strdup_printf("%s-%s.albuminfo",nartist,nalbum);
358 imgpath = gmpc_get_covers_path(filename);
359 g_free(filename);
360 fp = fopen(imgpath, "w");
361 if(fp)
363 int j=0,depth=0;;
364 *url = g_strdup(imgpath);
366 * Quick 'n Dirty html stripper
368 for(j=0; j < strlen(asi->album_info);j++)
370 if((asi->album_info)[j] == '<') depth++;
371 else if((asi->album_info)[j] == '>' && depth) depth--;
372 else if(depth == 0)
374 fputc((asi->album_info)[j],fp);
377 fclose(fp);
378 found = 1;
381 g_free(imgpath);
384 amazon_song_info_free(asi);
388 return found;
390 static int fetch_cover_art(mpd_Song *song,int type, char **url)
392 int retval=0,i=0;
393 gchar *artist = __cover_art_process_string(song->artist);
394 gchar *album = __cover_art_process_string(song->album);
396 while(!retval&&i<2)
397 retval = __fetch_metadata_amazon(search_types[i++], artist, album,type,url);
399 g_free(artist);
400 g_free(album);
401 return retval;
405 * Preferences
408 static void amazon_cover_art_enable_toggle(GtkWidget *wid)
410 int kk = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid));
411 cfg_set_single_value_as_int(config, "cover-amazon", "enable", kk);
413 static void amazon_cover_art_pref_destroy(GtkWidget *container)
415 gtk_container_remove(GTK_CONTAINER(container), wp_pref_vbox);
417 static void amazon_cover_art_pref_selection_changed(GtkWidget *box)
419 cfg_set_single_value_as_int(config, "cover-amazon", "location", gtk_combo_box_get_active(GTK_COMBO_BOX(box)));
422 static void amazon_cover_art_pref_construct(GtkWidget *container)
424 GtkWidget *enable_cg = gtk_check_button_new_with_mnemonic("_Enable amazon as cover art source");
425 GtkWidget *label = NULL;
426 GtkWidget *selection = NULL;
427 GtkWidget *entry = NULL;
428 GtkWidget *hbox = NULL;
429 int i;
430 wp_pref_vbox = gtk_vbox_new(FALSE,6);
432 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enable_cg),
433 cfg_get_single_value_as_int_with_default(config, "cover-amazon", "enable", TRUE));
435 g_signal_connect(G_OBJECT(enable_cg), "toggled", G_CALLBACK(amazon_cover_art_enable_toggle), NULL);
436 gtk_box_pack_start(GTK_BOX(wp_pref_vbox), enable_cg, FALSE, FALSE, 0);
438 /* Location */
439 hbox = gtk_hbox_new(FALSE, 6);
440 label = gtk_label_new("Location:");
441 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE,0);
442 selection = gtk_combo_box_new_text();
443 for(i=0;i<ENDPOINTS;i++)
444 gtk_combo_box_append_text(GTK_COMBO_BOX(selection), endpoints[i][1]);
445 gtk_combo_box_set_active(GTK_COMBO_BOX(selection),cfg_get_single_value_as_int_with_default(config, "cover-amazon", "location", 0));
446 g_signal_connect(G_OBJECT(selection), "changed",G_CALLBACK(amazon_cover_art_pref_selection_changed), NULL);
448 gtk_box_pack_start(GTK_BOX(hbox), selection, TRUE,TRUE,0);
449 gtk_box_pack_start(GTK_BOX(wp_pref_vbox), hbox, FALSE, FALSE, 0);
451 gtk_container_add(GTK_CONTAINER(container), wp_pref_vbox);
452 gtk_widget_show_all(container);
455 static int shrink_string(gchar *string, int start, int end)
457 int i;
459 for( i = start; i < end; i++)
460 string[i] = string[i + 1];
462 end--;
464 return end;