Print out the path that get passed to glyr.
[gmpc.git] / src / MetaData / metadata.c
blobdb8736c61ab46c1a67b52e11782d7f66b4388a0e
1 /* Gnome Music Player Client (GMPC)
2 * Copyright (C) 2004-2012 Qball Cow <qball@gmpclient.org>
3 * Project homepage: http://gmpclient.org/
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include <string.h>
21 #include <glib.h>
22 #include <glib/gstdio.h>
23 #include <ctype.h>
24 #include "main.h"
26 #include "metadata.h"
27 #include "preferences.h"
29 #define LOG_DOMAIN "MetaData"
31 #include <glyr/glyr.h>
32 #include <glyr/cache.h>
34 /**
35 * GLYR
37 static GAsyncQueue *gaq = NULL;
38 static GAsyncQueue *return_queue = NULL;
39 static GlyrDatabase *db = NULL;
42 /**
43 * This queue is used to send replies back.
45 enum MTD_Action {
46 MTD_ACTION_QUERY_METADATA,
47 MTD_ACTION_CLEAR_ENTRY,
48 MTD_ACTION_SET_ENTRY,
49 MTD_ACTION_QUERY_LIST,
50 MTD_ACTION_QUIT
53 /**
54 * Structure holding a metadata query */
55 typedef struct {
56 /* The type of action todo */
57 enum MTD_Action action;
58 /* unique id for the query (unused)*/
59 guint id;
60 /* The callback to call when the query is done, or NULL */
61 MetaDataCallback callback;
62 /* Callback user_data pointer*/
63 gpointer data;
64 /* The song the data is queries for */
65 mpd_Song *song;
67 /* Original (for sending the right signal) */
68 mpd_Song *ori_song;
70 /* The type of metadata */
71 MetaDataType type;
72 /* Result */
73 MetaDataResult result;
74 /* The actual result data */
75 MetaData *met;
76 GList *met_results;
77 } meta_thread_data;
80 // Validate if enough information is available for the query.
81 static gboolean meta_data_validate_query(mpd_Song *tsong, MetaDataType type)
83 switch(type&META_QUERY_DATA_TYPES)
85 case META_GENRE_SIMILAR:
86 if(tsong->genre == NULL || tsong->genre[0] == '\0')
87 return FALSE;
88 break;
89 case META_SONG_GUITAR_TAB:
90 case META_SONG_TXT:
91 case META_SONG_SIMILAR:
92 if(tsong->title == NULL || tsong->title[0] == '\0')
93 return FALSE;
94 if(tsong->artist == NULL || tsong->artist[0] == '\0')
95 return FALSE;
96 break;
97 case META_ALBUM_ART:
98 case META_ALBUM_TXT:
99 if(tsong->album == NULL || tsong->album[0] == '\0')
100 return FALSE;
101 case META_ARTIST_TXT:
102 case META_ARTIST_SIMILAR:
103 case META_BACKDROP_ART:
104 case META_ARTIST_ART:
105 if(tsong->artist == NULL|| tsong->artist[0] == '\0')
106 return FALSE;
107 break;
108 // other items.
109 case META_QUERY_DATA_TYPES:
110 case META_QUERY_NO_CACHE:
111 default:
112 return FALSE;
115 return TRUE;
119 static void meta_thread_data_free(meta_thread_data *mtd)
121 /* Free the result data */
122 if(mtd->met)
123 meta_data_free(mtd->met);
124 if(mtd->met_results) {
125 g_list_foreach(mtd->met_results, (GFunc)meta_data_free, NULL);
126 g_list_free(mtd->met_results);
127 mtd->met_results = NULL;
129 /* Free the copie and edited version of the songs */
130 if(mtd->song)
131 mpd_freeSong(mtd->song);
133 if(mtd->ori_song)
134 mpd_freeSong(mtd->ori_song);
136 /* Free the Request struct */
137 g_slice_free(meta_thread_data, mtd);
139 gboolean meta_compare_func(meta_thread_data *mt1, meta_thread_data *mt2);
140 //static gboolean meta_data_handle_results(void);
142 mpd_Song *rewrite_mpd_song(mpd_Song *tsong, MetaDataType type, gboolean query_mpd)
144 mpd_Song *edited = NULL;
145 /* If it is not a mpd got song */
146 if(tsong->file == NULL && query_mpd)
148 if(type&(META_ALBUM_ART|META_ALBUM_TXT) && tsong->artist && tsong->album)
150 MpdData *data2 = NULL;
151 mpd_database_search_start(connection, TRUE);
152 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ARTIST, tsong->artist);
153 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM, tsong->album);
154 data2 = mpd_database_search_commit(connection);
155 if(data2)
157 edited = data2->song;
158 data2->song = NULL;
159 mpd_data_free(data2);
161 else if(mpd_server_tag_supported(connection,MPD_TAG_ITEM_ALBUM_ARTIST) && tsong->albumartist)
163 mpd_database_search_start(connection, TRUE);
164 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM_ARTIST, tsong->albumartist);
165 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM, tsong->album);
166 data2 = mpd_database_search_commit(connection);
167 if(data2)
169 edited = data2->song;
170 data2->song = NULL;
171 mpd_data_free(data2);
177 else if(type&(META_ARTIST_ART|META_ARTIST_TXT) && tsong->artist)
179 MpdData *data2 = NULL;
180 mpd_database_search_start(connection, TRUE);
181 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ARTIST, tsong->artist);
182 data2 = mpd_database_search_commit(connection);
183 if(data2)
185 edited = data2->song;
186 data2->song = NULL;
187 mpd_data_free(data2);
191 if(!edited)
192 edited = mpd_songDup(tsong);
195 * Collections detection
196 * Only do this for album related queries.
198 if(type&(META_ALBUM_ART|META_ALBUM_TXT))
200 if(edited->albumartist)
202 if(edited->artist)
203 g_free(edited->artist);
204 edited->artist = g_strdup(edited->albumartist);
207 else if(edited->album && edited->file && query_mpd)
209 int i=0;
210 MpdData *data2;
211 char *dir = g_path_get_dirname(edited->file);
212 mpd_database_search_start(connection,TRUE);
213 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM, edited->album);
216 data2 = mpd_database_search_commit(connection);
217 if(data2)
219 for(i=0;data2; data2 = mpd_data_get_next(data2))
221 if(strncasecmp(data2->song->file, dir, strlen(dir))==0)
223 /* Check for NULL pointers */
224 if(data2->song->artist && edited->artist)
226 if(strcmp(data2->song->artist, edited->artist))
227 i++;
232 if(i >=3)
234 if(edited->artist)
235 g_free(edited->artist);
236 edited->artist = g_strdup("Various Artists");
238 g_free(dir);
242 * Artist renaming, Clapton, Eric -> Eric Clapton
243 if(edited->artist && cfg_get_single_value_as_int_with_default(config, "metadata", "rename", FALSE))
245 gchar **str = g_strsplit(edited->artist, ",", 2);
247 if(str[0] && str[1]) {
248 g_free(edited->artist);
249 edited->artist = g_strdup_printf("%s %s", g_strstrip(str[1]), g_strstrip(str[0]));
251 g_strfreev(str);
252 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "string converted to: '%s'", edited->artist);
256 * Sanitize album name and so (remove () (
258 if(cfg_get_single_value_as_int_with_default(config, "metadata", "sanitize", TRUE))
260 if(edited->album)
262 int i,j=0,depth=0;
263 int length;
264 char *album = edited->album;
265 edited->album = g_malloc0((strlen(album)+1)*sizeof(char));
266 length = strlen(album);
267 for(i=0;i< length;i++)
269 if(album[i] == '(') depth++;
270 else if (album[i] == ')')depth--;
271 else if (depth == 0) {
272 edited->album[j] = album[i];
273 j++;
276 g_free(album);
277 /* Remove trailing and leading spaces */
278 edited->album = g_strstrip(edited->album);
280 if(edited->title)
282 int i,j=0,depth=0;
283 char *title = edited->title;
284 int length;
285 edited->title = g_malloc0((strlen(title)+1)*sizeof(char));
286 length = strlen(title);
287 for(i=0;i< length;i++)
289 if(title[i] == '(') depth++;
290 else if (title[i] == ')')depth--;
291 else if (depth == 0) {
292 edited->title[j] = title[i];
293 j++;
296 g_free(title);
297 /* Remove trailing and leading spaces */
298 edited->title = g_strstrip(edited->title);
303 return edited;
306 * If the metadata thread managed to handle a result this function
307 * Will call the callback (from the main (gtk) thread)
309 static gboolean glyr_return_queue(void *user_data)
312 meta_thread_data *mtd = (meta_thread_data*)g_async_queue_try_pop(return_queue);
313 if(mtd)
315 printf("Process results: %i\n", mtd->action);
316 if(mtd->action == MTD_ACTION_QUERY_METADATA)
318 gmpc_meta_watcher_data_changed(gmw,mtd->ori_song, (mtd->type)&META_QUERY_DATA_TYPES, mtd->result,mtd->met);
319 if(mtd->callback)
321 mtd->callback(mtd->ori_song, mtd->result, mtd->met, mtd->data);
323 } else if (mtd->action == MTD_ACTION_QUERY_LIST)
325 if(mtd->callback){
326 MetaDataListCallback cb = (MetaDataListCallback)mtd->callback;
327 // TODO: Update callback for current usecase.
328 cb(NULL, "", mtd->met_results, mtd->data);
329 // Send done.
330 cb(NULL, "", NULL, mtd->data);
332 }else if (mtd->action == MTD_ACTION_CLEAR_ENTRY) {
333 printf("Signal no longer available.\n");
334 // Signal that this item is now no longer available.
335 gmpc_meta_watcher_data_changed(gmw, mtd->ori_song, (mtd->type)&META_QUERY_DATA_TYPES, META_DATA_UNAVAILABLE, NULL);
338 meta_thread_data_free(mtd);
339 return true;
341 return false;
344 static MetaDataContentType setup_glyr_query(GlyrQuery *query,
345 const meta_thread_data *mtd)
347 MetaDataContentType content_type = META_DATA_CONTENT_RAW;
348 MetaDataType type = mtd->type&META_QUERY_DATA_TYPES;
350 /* Force UTF 8 */
351 glyr_opt_force_utf8(query, TRUE);
353 glyr_opt_parallel(query, 1);
354 glyr_opt_number(query, 1);
355 // timeout 5 seconds.
356 glyr_opt_timeout(query, 5);
357 /* set metadata */
358 glyr_opt_artist(query,(char*)mtd->song->artist);
359 glyr_opt_album (query,(char*)mtd->song->album);
360 glyr_opt_title (query,(char*)mtd->song->title);
362 /* set default type */
363 glyr_opt_type(query, GLYR_GET_UNSURE);
365 if(type == META_ARTIST_ART )
367 glyr_opt_type(query, GLYR_GET_ARTIST_PHOTOS);
368 content_type = META_DATA_CONTENT_RAW;
370 else if (type == META_BACKDROP_ART)
372 glyr_opt_type(query, GLYR_GET_BACKDROPS);
373 content_type = META_DATA_CONTENT_RAW;
375 else if(type == META_ARTIST_TXT)
377 glyr_opt_lang_aware_only(query,TRUE);
378 glyr_opt_type(query, GLYR_GET_ARTISTBIO);
379 content_type = META_DATA_CONTENT_TEXT;
381 else if(type == META_ARTIST_SIMILAR)
383 glyr_opt_type(query, GLYR_GET_SIMILIAR_ARTISTS);
384 // cfg_* is no longer thread safe
385 glyr_opt_number(query,20);
386 content_type = META_DATA_CONTENT_TEXT;
388 else if(type == META_ALBUM_ART &&
389 mtd->song->album != NULL)
391 glyr_opt_type(query, GLYR_GET_COVERART);
392 content_type = META_DATA_CONTENT_RAW;
394 else if(type == META_ALBUM_TXT &&
395 mtd->song->album != NULL)
397 glyr_opt_type(query, GLYR_GET_ALBUM_REVIEW);
398 content_type = META_DATA_CONTENT_TEXT;
400 else if(type == META_SONG_TXT &&
401 mtd->song->title != NULL)
403 glyr_opt_type(query, GLYR_GET_LYRICS);
404 content_type = META_DATA_CONTENT_TEXT;
406 else if(type == META_SONG_SIMILAR &&
407 mtd->song->title != NULL)
409 glyr_opt_type(query, GLYR_GET_SIMILIAR_SONGS);
410 // cfg_* is no longer thread safe
411 glyr_opt_number(query,20);
413 else if (type == META_SONG_GUITAR_TAB &&
414 mtd->song->title)
416 glyr_opt_type(query, GLYR_GET_GUITARTABS);
417 content_type = META_DATA_CONTENT_TEXT;
419 else {
420 g_warning("Unsupported metadata type, or insufficient info");
422 return content_type;
425 * Convert cache to MetaData object.
427 static MetaData * glyr_get_similiar_song_names(GlyrMemCache * cache)
429 MetaData * mtd = NULL;
430 while(cache != NULL)
432 if(cache->data != NULL)
434 gchar ** split = g_strsplit(cache->data,"\n",0);
435 if(split != NULL && split[0] != NULL)
437 gchar * buffer;
438 if(!mtd) {
439 mtd = meta_data_new();
440 mtd->type = META_SONG_SIMILAR;
441 mtd->plugin_name = g_strdup(cache->prov);
442 mtd->content_type = META_DATA_CONTENT_TEXT_LIST;
443 mtd->size = 0;
446 buffer = g_strdup_printf("%s::%s",split[1],split[0]);
447 g_log(LOG_DOMAIN,G_LOG_LEVEL_DEBUG, "%s\n", buffer);
449 mtd->size++;
450 mtd->content = g_list_append((GList*) mtd->content, buffer);
451 g_strfreev(split);
454 cache = cache->next;
456 return mtd;
460 * Convert cache to MetaData object.
462 static MetaData * glyr_get_similiar_artist_names(GlyrMemCache * cache)
464 MetaData * mtd = NULL;
465 while(cache != NULL)
467 if(cache->data != NULL)
469 gchar ** split = g_strsplit(cache->data,"\n",0);
470 if(split != NULL)
472 if(!mtd) {
473 mtd = meta_data_new();
474 mtd->type = META_ARTIST_SIMILAR;
475 mtd->plugin_name = g_strdup(cache->prov);
476 mtd->content_type = META_DATA_CONTENT_TEXT_LIST;
477 mtd->size = 0;
479 mtd->size++;
480 mtd->content = g_list_append((GList*) mtd->content,
481 g_strdup((char *)split[0]));
482 g_strfreev(split);
485 cache = cache->next;
487 return mtd;
490 * Convert GLYR result into gmpc result
492 static gboolean process_glyr_result(GlyrMemCache *cache,
493 MetaDataContentType content_type,
494 meta_thread_data *mtd)
496 gboolean retv = FALSE;
497 // If more then one time called and we allready have a results
498 // Push it in the results list.
499 if(mtd->met != NULL) {
500 mtd->met_results = g_list_prepend(mtd->met_results, (void*)mtd->met);
502 mtd->result = META_DATA_UNAVAILABLE;
503 mtd->met = NULL;
504 if(cache == NULL) return retv;
505 if(cache->rating >= 0)
507 if(mtd->type == META_ARTIST_SIMILAR)
509 MetaData * cont = glyr_get_similiar_artist_names(cache);
510 if(cont != NULL)
512 (mtd->met) = cont;
513 mtd->result = META_DATA_AVAILABLE;
514 retv = TRUE;
517 else if (mtd->type == META_SONG_SIMILAR)
519 MetaData * cont;
520 cont = glyr_get_similiar_song_names(cache);
521 if (cont != NULL)
523 (mtd->met) = cont;
524 mtd->result = META_DATA_AVAILABLE;
525 retv = TRUE;
528 else
530 (mtd->met) = meta_data_new();
531 (mtd->met)->type = mtd->type;
532 if(cache->cached)
534 (mtd->met)->plugin_name = g_strdup_printf("%s (cached)", cache->prov);
535 }else{
536 (mtd->met)->plugin_name = g_strdup(cache->prov);
538 (mtd->met)->content_type = content_type;
540 // Steal the data.
541 mtd->met->content = cache->data;
542 cache->data = NULL;
543 (mtd->met)->size = cache->size;
544 cache->size = 0;
545 mtd->result = META_DATA_AVAILABLE;
546 // found something.
547 retv = TRUE;
549 memcpy(&(mtd->met->md5sum), &(cache->md5sum), 16);
550 }else {
551 // Explicitely not found.
552 printf("Cache sais empty\n");
553 retv = TRUE;
555 return retv;
558 static GlyrQuery *glyr_exit_handle = NULL;
559 static GStaticMutex exit_handle_lock = G_STATIC_MUTEX_INIT;
562 * Load a file from an URI
564 * @param mtd A meta_thread_data.
566 * Loads a file from a hard-drive.
568 * @returns nothing.
570 static GlyrMemCache *glyr_fetcher_thread_load_uri(meta_thread_data *mtd)
572 GlyrMemCache *cache = NULL;
573 const char *path = meta_data_get_uri(mtd->met);
574 gchar *scheme = g_uri_parse_scheme(path);
576 if(scheme == NULL || strcmp(scheme, "file") == 0)
578 char *content = NULL;
579 gsize length =0;
580 g_file_get_contents(path, &content,&length, NULL);
581 // set it to raw.
582 mtd->met->content_type = META_DATA_CONTENT_RAW;
583 cache = glyr_cache_new();
584 cache->dsrc = g_strdup(path);
585 glyr_cache_set_data(cache, content, length);
588 // Clean old content.
589 g_free(mtd->met->content);
590 mtd->met->content = g_memdup(content, length);
591 mtd->met->size = length;
592 content = NULL;
595 // Testing force image type.
596 if(mtd->met->type == META_ALBUM_ART || mtd->met->type == META_ARTIST_ART ||
597 mtd->met->type == META_BACKDROP_ART)
599 cache->is_image = TRUE;
600 cache->img_format = g_strdup("jpeg");
602 memcpy(&(mtd->met->md5sum), &(cache->md5sum), 16);
605 g_free(scheme);
606 return cache;
609 static GlyrMemCache *glyr_fetcher_thread_load_raw(meta_thread_data *mtd)
611 GlyrMemCache *cache = NULL;
612 cache = glyr_cache_new();
613 glyr_cache_set_data(cache,
614 g_memdup(mtd->met->content, mtd->met->size),
615 mtd->met->size);
616 // Testing force image type.
617 if(mtd->met->type == META_ALBUM_ART || mtd->met->type == META_ARTIST_ART ||
618 mtd->met->type == META_BACKDROP_ART)
620 cache->is_image = TRUE;
621 cache->img_format = g_strdup("jpeg");
623 memcpy(mtd->met->md5sum, cache->md5sum, 16);
624 return cache;
627 static GlyrMemCache *glyr_fetcher_thread_load_text(meta_thread_data *mtd)
629 GlyrMemCache *cache = NULL;
630 cache = glyr_cache_new();
631 glyr_cache_set_data(cache,
632 g_strdup(mtd->met->content),
633 -1);
634 memcpy(mtd->met->md5sum, cache->md5sum, 16);
635 return cache;
638 * Thread that does the GLYR requests
640 static void glyr_fetcher_thread(void *user_data)
642 void *d;
643 GlyrQuery query;
645 g_static_mutex_lock(&exit_handle_lock);
646 while((d = g_async_queue_pop(gaq)))
648 meta_thread_data *mtd = (meta_thread_data*)d;
650 // Check if this is the quit command.
651 if(mtd->action == MTD_ACTION_QUIT) {
652 printf("Quitting....");
653 g_static_mutex_unlock(&exit_handle_lock);
654 /* Free the Request struct */
655 meta_thread_data_free(mtd);
656 return;
658 else if (mtd->action == MTD_ACTION_CLEAR_ENTRY)
660 GlyrMemCache *cache = NULL;
662 // Setup cancel lock
663 glyr_exit_handle = &query;
664 g_static_mutex_unlock(&exit_handle_lock);
666 /* Set up the query */
667 glyr_query_init(&query);
668 setup_glyr_query(&query, mtd);
669 glyr_opt_number(&query, 0);
670 /* Set some random settings */
671 glyr_opt_verbosity(&query,4);
673 // Delete existing entries.
674 glyr_db_delete(db, &query);
676 // Set dummy entry in cache, so we know
677 // we searched for this before.
678 cache = glyr_cache_new();
679 // TODO: Remove this randomize hack.
680 glyr_cache_set_data(cache,
681 g_strdup("GMPC Dummy"),
682 -1);
683 cache->rating = -1;
685 // Add dummy entry
686 printf("Inserting dummy item\n");
687 glyr_db_insert(db,&query, cache);
689 // Cleanup
690 if(cache)glyr_free_list(cache);
692 // Clear the query, and lock the handle again.
693 g_static_mutex_lock(&exit_handle_lock);
694 glyr_exit_handle = NULL;
695 glyr_query_destroy(&query);
697 printf("Push back result\n");
698 // Push back result, and tell idle handle to handle it.
699 g_async_queue_push(return_queue, mtd);
700 // invalidate pointer.
701 mtd = NULL;
702 // Schedule the result thread in idle time.
703 g_idle_add(glyr_return_queue, NULL);
705 else if (mtd->action == MTD_ACTION_QUERY_LIST)
707 // Check if this is thread safe.
708 const char *md = connection_get_music_directory();
709 GLYR_ERROR err = GLYRE_OK;
710 MetaDataContentType content_type = META_DATA_CONTENT_RAW;
711 GlyrMemCache *cache = NULL;
713 glyr_exit_handle = &query;
714 g_static_mutex_unlock(&exit_handle_lock);
716 printf("new style query\n");
718 glyr_query_init(&query);
721 if(md != NULL && md[0] != '\0'&& mtd->song->file != NULL)
723 char *path = g_build_filename(md, mtd->song->file, NULL);
724 printf("music directory: \"%s\"\n", path);
725 glyr_opt_musictree_path(&query, path);
726 g_free(path);
730 /* Set up the query */
731 content_type = setup_glyr_query(&query, mtd);
733 /* Set some random settings */
734 glyr_opt_verbosity(&query,3);
736 /* Tell libglyr to automatically lookup before searching the web */
737 glyr_opt_lookup_db(&query, db);
739 /* Also tell it not to write newly found items to the db */
740 glyr_opt_db_autowrite(&query, FALSE);
741 /* We want many results */
742 glyr_opt_number(&query, 25);
744 /* get metadata */
745 cache = glyr_get(&query,&err,NULL);
747 if(cache != NULL)
749 GlyrMemCache *iter = cache;
750 while(iter){
751 process_glyr_result(iter,content_type, mtd);
752 iter = iter->next;
754 // Done. (most last item into list)
755 process_glyr_result(NULL,content_type, mtd);
757 // Cleanup
758 if(cache)glyr_free_list(cache);
760 // Clear the query, and lock the handle again.
761 g_static_mutex_lock(&exit_handle_lock);
762 glyr_exit_handle = NULL;
763 glyr_query_destroy(&query);
765 // Push back result, and tell idle handle to handle it.
766 g_async_queue_push(return_queue, mtd);
767 // invalidate pointer.
768 mtd = NULL;
769 // Schedule the result thread in idle time.
770 g_idle_add(glyr_return_queue, NULL);
772 // QUERY database
773 else if (mtd->action == MTD_ACTION_QUERY_METADATA)
775 // Check if this is thread safe.
776 const char *md = connection_get_music_directory();
777 GLYR_ERROR err = GLYRE_OK;
778 MetaDataContentType content_type = META_DATA_CONTENT_RAW;
779 GlyrMemCache *cache = NULL;
781 glyr_exit_handle = &query;
782 g_static_mutex_unlock(&exit_handle_lock);
784 printf("new style query\n");
786 glyr_query_init(&query);
789 if(md != NULL && md[0] != '\0'&& mtd->song->file != NULL)
791 char *path = g_build_filename(md, mtd->song->file, NULL);
792 printf("music directory: \"%s\"\n", path);
793 glyr_opt_musictree_path(&query, path);
794 g_free(path);
798 /* Set up the query */
799 content_type = setup_glyr_query(&query, mtd);
801 /* If cache disabled, remove the entry in the db */
802 if(((mtd->type)&META_QUERY_NO_CACHE) == META_QUERY_NO_CACHE)
804 printf("Disable cache\n");
805 glyr_opt_from(&query, "all;-local");
806 // Remove cache request.
807 mtd->type&=META_QUERY_DATA_TYPES;
808 // Delete the entry.
809 glyr_db_delete(db, &query);
812 /* Set some random settings */
813 glyr_opt_verbosity(&query,3);
815 /* Tell libglyr to automatically lookup before searching the web */
816 glyr_opt_lookup_db(&query, db);
818 /* Also tell it to write newly found items to the db */
819 glyr_opt_db_autowrite(&query, TRUE);
822 /* get metadata */
823 cache = glyr_get(&query,&err,NULL);
825 if(cache == NULL){
826 // Set dummy entry in cache, so we know
827 // we searched for this before.
828 cache = glyr_cache_new();
829 // TODO: Remove this randomize hack.
830 glyr_cache_set_data(cache,
831 g_strdup("GMPC Dummy"),
832 -1);
833 cache->rating = -1;
835 glyr_db_insert(db,&query, cache);
836 printf("Cache is Empty\n");
837 // Set unavailable
838 mtd->result = META_DATA_UNAVAILABLE;
839 }else{
840 process_glyr_result(cache,content_type, mtd);
842 // Cleanup
843 if(cache)glyr_free_list(cache);
845 // Clear the query, and lock the handle again.
846 g_static_mutex_lock(&exit_handle_lock);
847 glyr_exit_handle = NULL;
848 glyr_query_destroy(&query);
850 // Push back result, and tell idle handle to handle it.
851 g_async_queue_push(return_queue, mtd);
852 // invalidate pointer.
853 mtd = NULL;
854 // Schedule the result thread in idle time.
855 g_idle_add(glyr_return_queue, NULL);
857 else if (mtd->action == MTD_ACTION_SET_ENTRY)
859 GlyrMemCache *cache = NULL;
861 // Setup cancel lock
862 glyr_exit_handle = &query;
863 g_static_mutex_unlock(&exit_handle_lock);
865 /* Set up the query */
866 glyr_query_init(&query);
867 setup_glyr_query(&query, mtd);
868 glyr_opt_number(&query, 0);
869 /* Set some random settings */
870 glyr_opt_verbosity(&query,3);
872 // Delete existing entries.
873 glyr_db_delete(db, &query);
875 glyr_query_init(&query);
876 setup_glyr_query(&query, mtd);
877 glyr_opt_number(&query, 0);
878 /* Set some random settings */
879 glyr_opt_verbosity(&query,3);
880 // load data
881 if(meta_data_is_uri(mtd->met))
883 // try to load cache from file.
884 cache = glyr_fetcher_thread_load_uri(mtd);
886 else if (meta_data_is_raw(mtd->met))
888 // try to load cache from raw.
889 cache = glyr_fetcher_thread_load_raw(mtd);
890 } else if (meta_data_is_text(mtd->met) || meta_data_is_html(mtd->met))
892 // try to load cache from text.
893 cache = glyr_fetcher_thread_load_text(mtd);
896 // Cache.
897 if(cache)
899 cache->rating = 9;
900 printf("Do DB insert\n");
901 glyr_db_insert(db,&query, cache);
904 // Clear the query, and lock the handle again.
905 g_static_mutex_lock(&exit_handle_lock);
906 glyr_exit_handle = NULL;
907 glyr_query_destroy(&query);
909 // Push back result, and tell idle handle to handle it.
910 // set it to query metadata to get the right handle behaviour.
911 mtd->action = MTD_ACTION_QUERY_METADATA;
912 g_async_queue_push(return_queue, mtd);
913 // invalidate pointer.
914 mtd = NULL;
917 // Schedule the result thread in idle time.
918 g_idle_add(glyr_return_queue, NULL);
919 }else {
920 g_error("Unknown type of query to perform");
921 return;
927 * Initialize
929 GThread *gaq_fetcher_thread = NULL;
930 void meta_data_init(void)
932 gchar *url;
934 /* Is this function thread safe? */
935 url = gmpc_get_covers_path("");
937 //g_mutex_init(&exit_handle_lock);
938 /* Initialize..*/
939 printf("open glyr db: %s\n", url);
940 glyr_init();
941 db = glyr_db_init(url);
942 g_free(url);
945 gaq = g_async_queue_new();
946 return_queue = g_async_queue_new();
947 #if GLIB_CHECK_VERSION(2, 31, 0)
948 gaq_fetcher_thread = g_thread_new("Glyr thread fetch", (GThreadFunc)glyr_fetcher_thread, NULL);
949 #else
950 gaq_fetcher_thread = g_thread_create(glyr_fetcher_thread, NULL, TRUE, NULL);
951 #endif
956 * TODO: Can we guarantee that all the downloads are stopped?
958 void meta_data_destroy(void)
960 meta_thread_data *mtd = NULL;
962 * Clear the request queue, and tell thread to quit
964 g_async_queue_lock(gaq);
965 while((mtd = g_async_queue_try_pop_unlocked(gaq))){
966 /* Free */
967 meta_thread_data_free(mtd);
969 mtd = g_slice_new0(meta_thread_data);//g_malloc0(sizeof(*mtd));
970 mtd->action = MTD_ACTION_QUIT;
971 g_async_queue_push_unlocked(gaq, mtd);
972 mtd = NULL;
973 g_async_queue_unlock(gaq);
974 // add lock?
975 g_static_mutex_lock(&exit_handle_lock);
976 if(glyr_exit_handle) {
977 printf("Sending quit signal\n");
978 glyr_signal_exit(glyr_exit_handle);
980 g_static_mutex_unlock(&exit_handle_lock);
982 printf("Waiting for glyr to finish.....\n");
983 g_thread_join(gaq_fetcher_thread);
984 //g_mutex_clear(&exit_handle_lock);
986 glyr_db_destroy(db);
987 glyr_cleanup();
989 * Wait for thread to quit
991 g_async_queue_lock(return_queue);
992 while((mtd = g_async_queue_try_pop_unlocked(return_queue))){
993 /* Free any possible plugin results */
994 meta_thread_data_free(mtd);
996 g_async_queue_unlock(return_queue);
997 g_async_queue_unref(gaq);
998 g_async_queue_unref(return_queue);
1001 gboolean meta_compare_func(meta_thread_data *mt1, meta_thread_data *mt2)
1003 if(mt1->action != mt2->action) return TRUE;
1005 if((mt1->type&META_QUERY_DATA_TYPES) != (mt2->type&META_QUERY_DATA_TYPES))
1006 return TRUE;
1007 if(!gmpc_meta_watcher_match_data(mt1->type&META_QUERY_DATA_TYPES, mt1->song, mt2->song))
1009 return TRUE;
1011 return FALSE;
1015 static guint meta_data_thread_data_uid = 0;
1017 void meta_data_set_entry ( mpd_Song *song, MetaData *met )
1019 meta_thread_data *mtd = NULL;
1020 if(song == NULL || met == NULL || !meta_data_validate_query(song, met->type))
1022 g_warning("Trying to set metadata entry with insufficient information");
1023 return;
1026 mtd = g_slice_new0(meta_thread_data);
1027 mtd->action = MTD_ACTION_SET_ENTRY;
1028 mtd->id = ++meta_data_thread_data_uid;
1029 /* Create a copy of the original song */
1030 mtd->song = rewrite_mpd_song(song, met->type, TRUE);
1031 mtd->ori_song = mpd_songDup(song);
1032 /* Set the type */
1033 mtd->type = met->type;
1034 /* set result NULL */
1035 mtd->met = meta_data_dup(met);;
1036 /* signal we are fetching. */
1037 gmpc_meta_watcher_data_changed(gmw,mtd->ori_song, (mtd->type)&META_QUERY_DATA_TYPES, META_DATA_FETCHING,NULL);
1038 /* Set entry */
1039 printf("Request setting entry\n");
1040 g_async_queue_push(gaq, mtd);
1041 mtd = NULL;
1044 void meta_data_clear_entry(mpd_Song *song, MetaDataType type)
1046 meta_thread_data *mtd = NULL;
1047 if(!meta_data_validate_query(song, type))
1049 g_warning("Trying to clear metadata entry with insufficient information");
1050 return;
1052 mtd = g_slice_new0(meta_thread_data);
1053 mtd->action = MTD_ACTION_CLEAR_ENTRY;
1054 mtd->id = ++meta_data_thread_data_uid;
1055 /* Create a copy of the original song */
1056 mtd->song = rewrite_mpd_song(song, type,TRUE);
1057 mtd->ori_song = mpd_songDup(song);
1058 /* Set the type */
1059 mtd->type = type;
1060 /* set result NULL */
1061 mtd->met = NULL;
1062 printf("Request clearing entry\n");
1063 g_async_queue_push(gaq, mtd);
1064 mtd = NULL;
1068 * Function called by the "client"
1071 MetaDataResult meta_data_get_path(mpd_Song *tsong, MetaDataType type, MetaData **met,MetaDataCallback callback, gpointer data)
1073 meta_thread_data *mtd = NULL;
1075 if(!meta_data_validate_query(tsong, type))
1077 printf("Query invalid");
1078 *met = NULL;
1079 return META_DATA_UNAVAILABLE;
1082 mtd = g_slice_new0(meta_thread_data);
1083 mtd->action = MTD_ACTION_QUERY_METADATA;
1084 mtd->id = ++meta_data_thread_data_uid;
1085 /* Create a copy of the original song */
1086 mtd->song = rewrite_mpd_song(tsong, type, FALSE);
1087 mtd->ori_song = mpd_songDup(tsong);
1088 /* Set the type */
1089 mtd->type = type;
1090 /* the callback */
1091 mtd->callback = callback;
1092 /* the callback data */
1093 mtd->data = data;
1094 /* Set that we are fetching */
1095 mtd->result = META_DATA_FETCHING;
1096 /* set result NULL */
1097 mtd->met = NULL;
1099 * If requested query the cache first
1101 if((type&META_QUERY_NO_CACHE) == 0)
1103 MetaDataResult mrd;
1104 MetaDataContentType content_type = META_DATA_CONTENT_RAW;
1105 GlyrQuery query;
1106 GlyrMemCache * cache = NULL;
1108 glyr_query_init(&query);
1109 /* Set some random settings */
1110 glyr_opt_verbosity(&query,3);
1112 content_type = setup_glyr_query(&query, mtd);
1113 cache = glyr_db_lookup(db, &query);
1114 if(process_glyr_result(cache,content_type, mtd))
1116 // Cleanup
1117 if(cache)glyr_free_list(cache);
1118 glyr_query_destroy(&query);
1120 mrd = mtd->result;
1121 *met = mtd->met;
1122 mtd->met = NULL;
1123 // Free mtd
1124 meta_thread_data_free(mtd);
1126 return mrd;
1128 if(cache)glyr_free_list(cache);
1129 glyr_query_destroy(&query);
1131 else
1133 printf("signal fetching\n");
1134 gmpc_meta_watcher_data_changed(gmw,mtd->ori_song, (mtd->type)&META_QUERY_DATA_TYPES, META_DATA_FETCHING,NULL);
1135 if(mtd->callback)
1137 mtd->callback(mtd->ori_song, META_DATA_FETCHING, NULL, mtd->data);
1141 // Rewrite for query
1142 if(mtd->song != NULL) mpd_freeSong(mtd->song);
1143 mtd->song = rewrite_mpd_song(tsong, type, TRUE);
1146 g_async_queue_push(gaq, mtd);
1147 mtd = NULL;
1148 return META_DATA_FETCHING;
1151 static gchar * strip_invalid_chars(gchar *input)
1153 int i = 0;
1154 int length = 0;
1155 g_assert(input != NULL);
1156 length = strlen(input);
1157 if(input == NULL) return NULL;
1158 for(i=0;i<length;i++)
1160 switch(input[i])
1162 case ':':
1163 case '\\':
1164 case '/':
1165 case ';':
1166 case '*':
1167 case '?':
1168 case '\"':
1169 case '<':
1170 case '>':
1171 case '|':
1172 input[i] = ' ';
1173 default:
1174 break;
1177 return input;
1180 * Helper function for storing metadata
1182 gchar * gmpc_get_metadata_filename(MetaDataType type, mpd_Song *song, char *ext)
1184 gchar *retv= NULL;
1185 /* home dir */
1186 const gchar *homedir = g_get_user_cache_dir();
1187 g_assert(song->artist != NULL);
1188 g_assert(type < META_QUERY_DATA_TYPES);
1191 GError *error = NULL;
1192 gchar *filename = NULL, *dirname = NULL;
1193 const gchar *extension= (type&(META_ALBUM_TXT|META_ARTIST_TXT|META_SONG_TXT|META_SONG_GUITAR_TAB))?"txt":((ext == NULL)?((type&(META_ALBUM_ART|META_ARTIST_ART))?"jpg":""):ext);
1195 /* Convert it so the filesystem likes it */
1196 /* TODO: Add error checking */
1198 dirname = g_filename_from_utf8(song->artist, -1, NULL, NULL, NULL);
1199 if(dirname == NULL)
1201 const gchar *charset;
1202 g_get_charset (&charset);
1203 dirname = g_convert_with_fallback (song->artist, -1,
1204 charset, "UTF-8",(char *)"-", NULL, NULL, &error);
1206 if(error) {
1207 g_log(LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Failed to convert %s to file encoding. '%s'", song->artist, error->message);
1208 g_error_free(error);
1209 if(dirname) g_free(dirname);
1210 dirname = g_strdup("invalid");
1212 dirname = strip_invalid_chars(dirname);
1213 retv = g_build_path(G_DIR_SEPARATOR_S, homedir,"gmpc","metadata", dirname,NULL);
1214 if(g_file_test(retv, G_FILE_TEST_EXISTS) == FALSE) {
1215 if(g_mkdir_with_parents(retv, 0755) < 0) {
1216 g_error("Failed to create: %s\n", retv);
1217 abort();
1220 if(!g_file_test(retv, G_FILE_TEST_IS_DIR)) {
1221 g_error("File exists but is not a directory: %s\n", retv);
1222 abort();
1224 g_free(retv);
1225 if(type&(META_ALBUM_ART|META_ALBUM_TXT)) {
1226 gchar *temp ;
1227 g_assert(song->album != NULL);
1228 temp =g_filename_from_utf8(song->album,-1,NULL,NULL,NULL);
1229 filename = g_strdup_printf("%s.%s", temp,extension);
1230 g_free(temp);
1231 }else if(type&META_ARTIST_ART){
1232 filename = g_strdup_printf("artist_IMAGE.%s", extension);
1233 }else if (type&META_ARTIST_TXT){
1234 filename = g_strdup_printf("artist_BIOGRAPHY.%s", extension);
1235 }else if (type&META_SONG_TXT) {
1236 gchar *temp ;
1237 g_assert(song->title != NULL);
1238 temp =g_filename_from_utf8(song->title,-1,NULL,NULL,NULL);
1239 filename = g_strdup_printf("%s_LYRIC.%s", temp,extension);
1240 g_free(temp);
1241 }else if (type&META_SONG_GUITAR_TAB) {
1242 gchar *temp ;
1243 g_assert(song->title != NULL);
1244 temp =g_filename_from_utf8(song->title,-1,NULL,NULL,NULL);
1245 filename = g_strdup_printf("%s_GUITAR_TAB.%s", temp,extension);
1246 g_free(temp);
1248 filename = strip_invalid_chars(filename);
1249 retv = g_build_path(G_DIR_SEPARATOR_S, homedir,"gmpc", "metadata", dirname,filename,NULL);
1250 if(filename) g_free(filename);
1251 if(dirname) g_free(dirname);
1253 return retv;
1256 * Set enabled
1259 void metadata_get_list_cancel(gpointer data)
1263 gpointer metadata_get_list(mpd_Song *song, MetaDataType type, void (*callback)(gpointer handle,const gchar *plugin_name, GList *list, gpointer data), gpointer data)
1265 meta_thread_data *mtd = NULL;
1267 mtd = g_slice_new0(meta_thread_data);
1268 mtd->action = MTD_ACTION_QUERY_LIST;
1269 mtd->id = ++meta_data_thread_data_uid;
1270 /* Create a copy of the original song */
1271 mtd->song = rewrite_mpd_song(song, type, TRUE);
1272 mtd->ori_song = mpd_songDup(song);
1273 /* Set the type */
1274 mtd->type = type;
1275 /* the callback */
1276 mtd->callback = callback;
1277 /* the callback data */
1278 mtd->data = data;
1279 /* Set that we are fetching */
1280 mtd->result = META_DATA_FETCHING;
1281 /* set result NULL */
1282 mtd->met = NULL;
1283 mtd->met_results = NULL;
1284 printf("start query\n");
1286 g_async_queue_push(gaq, mtd);
1287 mtd = NULL;
1288 return NULL;
1291 * MetaData
1293 MetaData *meta_data_new(void)
1295 /* Create a new structure completely filled with 0's */
1296 MetaData *retv = g_new0(MetaData, 1);
1297 retv->content_type = META_DATA_CONTENT_EMPTY;
1298 return retv;
1301 void meta_data_free(MetaData *data)
1303 if(data == NULL) return;
1304 if(data->content) {
1305 if(data->content_type == META_DATA_CONTENT_TEXT_VECTOR)
1307 g_strfreev(data->content);
1309 else if (data->content_type == META_DATA_CONTENT_TEXT_LIST)
1311 g_list_foreach((GList *)data->content, (GFunc)g_free, NULL);
1312 g_list_free((GList *)data->content);
1314 else if(data->content_type != META_DATA_CONTENT_EMPTY)
1315 g_free(data->content);
1316 data->content = NULL;
1317 data->size = 0;
1319 if(data->thumbnail_uri) g_free(data->thumbnail_uri);
1320 data->thumbnail_uri = NULL;
1321 if(data->plugin_name) g_free(data->plugin_name);
1323 g_free(data);
1326 MetaData *meta_data_dup(MetaData *data)
1328 MetaData *retv = meta_data_new();
1329 g_assert(data != NULL);
1330 /* Copy type of metadata */
1331 retv->type = data->type;
1332 /* Copy the type of the data */
1333 retv->content_type = data->content_type;
1334 /* Copy the name of the providing plugin. (const char * so only copy pointer ) */
1335 retv->plugin_name = g_strdup(data->plugin_name);
1336 /* copy the content */
1337 retv->size = data->size;
1338 if(retv->content_type == META_DATA_CONTENT_TEXT_VECTOR) {
1339 if(data->content) retv->content =(void *) g_strdupv((gchar **)data->content);
1341 /* raw data always needs a length */
1342 else if (data->content_type == META_DATA_CONTENT_RAW)
1344 if(data->size > 0 ) {
1345 retv->content = g_memdup(data->content, (guint)data->size);
1348 else if (data->content_type == META_DATA_CONTENT_TEXT_LIST)
1350 GList *list = NULL;
1351 GList *iter = g_list_first((GList *)(data->content));
1352 while((iter = g_list_next(iter)))
1354 list = g_list_append(list, g_strdup(iter->data));
1356 data->content =(void *) g_list_reverse(list);
1358 else if (data->content_type == META_DATA_CONTENT_EMPTY)
1360 retv->content = NULL; retv->size = 0;
1362 /* Text is NULL terminated */
1363 else
1365 retv->content = NULL;
1366 if(data->content){
1367 retv->content = g_strdup((gchar *)data->content);
1370 if(data->thumbnail_uri != NULL) {
1371 retv->thumbnail_uri = g_strdup(data->thumbnail_uri);
1374 memcpy(&(retv->md5sum),&(data->md5sum), 16);
1376 return retv;
1378 MetaData *meta_data_dup_steal(MetaData *data)
1380 MetaData *retv = meta_data_new();
1381 g_assert(data != NULL);
1382 /* Copy type of metadata */
1383 retv->type = data->type;
1384 /* Copy the type of the data */
1385 retv->content_type = data->content_type;
1386 /* Copy the name of the providing plugin. (const char * so only copy pointer ) */
1387 retv->plugin_name = g_strdup(data->plugin_name);
1388 /* copy the content */
1389 retv->size = data->size;
1390 retv->content = data->content;
1391 data->size = 0;
1392 data->content = NULL;
1393 retv->thumbnail_uri = data->thumbnail_uri;
1394 data->thumbnail_uri = NULL;
1396 memcpy(&(retv->md5sum),&(data->md5sum), 16);
1398 return retv;
1400 gboolean meta_data_is_empty(const MetaData *data)
1402 return data->content_type == META_DATA_CONTENT_EMPTY;
1404 gboolean meta_data_is_uri(const MetaData *data)
1406 return data->content_type == META_DATA_CONTENT_URI;
1408 const gchar * meta_data_get_uri(const MetaData *data)
1410 g_assert(meta_data_is_uri(data));
1411 return (const gchar *)data->content;
1413 void meta_data_set_uri(MetaData *data, const gchar *uri)
1415 g_assert(meta_data_is_uri(data));
1416 if(data->content) g_free(data->content);
1417 data->content = g_strdup(uri);
1419 void meta_data_set_raw(MetaData *item, guchar *data, gsize len)
1421 g_assert(meta_data_is_raw(item));
1422 if(item->content) g_free(item->content);
1423 item->content = g_memdup(data, len);
1424 item->size = len;
1426 void meta_data_set_raw_owned(MetaData *item, guchar **data, gsize *len)
1428 g_assert(meta_data_is_raw(item));
1429 if(item->content) g_free(item->content);
1430 item->content = *data;
1431 *data = NULL;
1432 item->size = *len;
1433 *len = 0;
1435 void meta_data_set_thumbnail_uri(MetaData *data, const gchar *uri)
1437 g_assert(meta_data_is_uri(data));
1438 if(data->thumbnail_uri) g_free(data->thumbnail_uri);
1439 data->thumbnail_uri = g_strdup(uri);
1441 const gchar * meta_data_get_thumbnail_uri(const MetaData *data)
1443 g_assert(meta_data_is_uri(data));
1444 /* Only valid for images. */
1445 g_assert((data->type&(META_ALBUM_ART|META_ARTIST_ART)) != 0);
1447 return (const gchar *)data->thumbnail_uri;
1450 gboolean meta_data_is_text(const MetaData *data)
1452 return data->content_type == META_DATA_CONTENT_TEXT;
1455 void meta_data_set_text(MetaData *data, const gchar *text)
1457 if(meta_data_is_text(data))
1459 if(data->content) g_free(data->content);
1460 data->content = g_strdup(text);
1461 data->size = -1;
1464 const gchar * meta_data_get_text(const MetaData *data)
1466 g_assert(meta_data_is_text(data));
1467 return (const gchar *)data->content;
1470 gboolean meta_data_is_html(const MetaData *data)
1472 return data->content_type == META_DATA_CONTENT_HTML;
1474 const gchar * meta_data_get_html(const MetaData *data)
1476 g_assert(meta_data_is_html(data));
1477 return (const gchar *)data->content;
1482 * Convert a html encoded token (like &amp; and &#030;
1483 * to the corresponding unichar.
1485 * On early falire (i.e. empty string) returns 0.
1486 * If the encoding is wrong, it returns the unicode error value 0xFFFC.
1488 static gunichar htmlname2unichar( const gchar* htmlname, gint len )
1490 // * can be optimized by sorting
1491 // * and checking str(n)cmp results
1492 static struct _TT {
1493 const gchar *str;
1494 gint utf8;
1496 html2utf8_table[] = {
1497 { "quot", 34 },
1498 { "amp", 38 },
1499 { "lt", 60 },
1500 { "gt", 62 },
1501 { "nbsp", 160 },
1502 { "iexcl", 161 },
1503 { "cent", 162 },
1504 { "pound", 163 },
1505 { "curren", 164 },
1506 { "yen", 165 },
1507 { "brvbar", 166 },
1508 { "sect", 167 },
1509 { "uml", 168 },
1510 { "copy", 169 },
1511 { "ordf", 170 },
1512 { "laquo", 171 },
1513 { "not", 172 },
1514 { "shy", 173 },
1515 { "reg", 174 },
1516 { "macr", 175 },
1517 { "deg", 176 },
1518 { "plusmn", 177 },
1519 { "sup2", 178 },
1520 { "sup3", 179 },
1521 { "acute", 180 },
1522 { "micro", 181 },
1523 { "para", 182 },
1524 { "middot", 183 },
1525 { "cedil", 184 },
1526 { "sup1", 185 },
1527 { "ordm", 186 },
1528 { "raquo", 187 },
1529 { "frac14", 188 },
1530 { "frac12", 189 },
1531 { "frac34", 190 },
1532 { "iquest", 191 },
1533 { "Agrave", 192 },
1534 { "Aacute", 193 },
1535 { "Acirc", 194 },
1536 { "Atilde", 195 },
1537 { "Auml", 196 },
1538 { "Aring", 197 },
1539 { "AElig", 198 },
1540 { "Ccedil", 199 },
1541 { "Egrave", 200 },
1542 { "Eacute", 201 },
1543 { "Ecirc", 202 },
1544 { "Euml", 203 },
1545 { "Igrave", 204 },
1546 { "Iacute", 205 },
1547 { "Icirc", 206 },
1548 { "Iuml", 207 },
1549 { "ETH", 208 },
1550 { "Ntilde", 209 },
1551 { "Ograve", 210 },
1552 { "Oacute", 211 },
1553 { "Ocirc", 212 },
1554 { "Otilde", 213 },
1555 { "Ouml", 214 },
1556 { "times", 215 },
1557 { "Oslash", 216 },
1558 { "Ugrave", 217 },
1559 { "Uacute", 218 },
1560 { "Ucirc", 219 },
1561 { "Uuml", 220 },
1562 { "Yacute", 221 },
1563 { "THORN", 222 },
1564 { "szlig", 223 },
1565 { "agrave", 224 },
1566 { "aacute", 225 },
1567 { "acirc", 226 },
1568 { "atilde", 227 },
1569 { "auml", 228 },
1570 { "aring", 229 },
1571 { "aelig", 230 },
1572 { "ccedil", 231 },
1573 { "egrave", 232 },
1574 { "eacute", 233 },
1575 { "ecirc", 234 },
1576 { "euml", 235 },
1577 { "igrave", 236 },
1578 { "iacute", 237 },
1579 { "icirc", 238 },
1580 { "iuml", 239 },
1581 { "eth", 240 },
1582 { "ntilde", 241 },
1583 { "ograve", 242 },
1584 { "oacute", 243 },
1585 { "ocirc", 244 },
1586 { "otilde", 245 },
1587 { "ouml", 246 },
1588 { "divide", 247 },
1589 { "oslash", 248 },
1590 { "ugrave", 249 },
1591 { "uacute", 250 },
1592 { "ucirc", 251 },
1593 { "uuml", 252 },
1594 { "yacute", 253 },
1595 { "thorn", 254 },
1596 { "yuml", 255 },
1597 { "Alpha", 913 },
1598 { "alpha", 945 },
1599 { "Beta", 914 },
1600 { "beta", 946 },
1601 { "Gamma", 915 },
1602 { "gamma", 947 },
1603 { "Delta", 916 },
1604 { "delta", 948 },
1605 { "Epsilon", 917 },
1606 { "epsilon", 949 },
1607 { "Zeta", 918 },
1608 { "zeta", 950 },
1609 { "Eta", 919 },
1610 { "eta", 951 },
1611 { "Theta", 920 },
1612 { "theta", 952 },
1613 { "Iota", 921 },
1614 { "iota", 953 },
1615 { "Kappa", 922 },
1616 { "kappa", 954 },
1617 { "Lambda", 923 },
1618 { "lambda", 955 },
1619 { "Mu", 924 },
1620 { "mu", 956 },
1621 { "Nu", 925 },
1622 { "nu", 957 },
1623 { "Xi", 926 },
1624 { "xi", 958 },
1625 { "Omicron", 927 },
1626 { "omicron", 959 },
1627 { "Pi", 928 },
1628 { "pi", 960 },
1629 { "Rho", 929 },
1630 { "rho", 961 },
1631 { "Sigma", 931 },
1632 { "sigmaf", 962 },
1633 { "sigma", 963 },
1634 { "Tau", 932 },
1635 { "tau", 964 },
1636 { "Upsilon", 933 },
1637 { "upsilon", 965 },
1638 { "Phi", 934 },
1639 { "phi", 966 },
1640 { "Chi", 935 },
1641 { "chi", 967 },
1642 { "Psi", 936 },
1643 { "psi", 968 },
1644 { "Omega", 937 },
1645 { "omega", 969 },
1646 { "thetasym", 977 },
1647 { "upsih", 978 },
1648 { "piv", 982 },
1649 { "forall", 8704 },
1650 { "part", 8706 },
1651 { "exist", 8707 },
1652 { "empty", 8709 },
1653 { "nabla", 8711 },
1654 { "isin", 8712 },
1655 { "notin", 8713 },
1656 { "ni", 8715 },
1657 { "prod", 8719 },
1658 { "sum", 8721 },
1659 { "minus", 8722 },
1660 { "lowast", 8727 },
1661 { "radic", 8730 },
1662 { "prop", 8733 },
1663 { "infin", 8734 },
1664 { "ang", 8736 },
1665 { "and", 8869 },
1666 { "or", 8870 },
1667 { "cap", 8745 },
1668 { "cup", 8746 },
1669 { "int", 8747 },
1670 { "there4", 8756 },
1671 { "sim", 8764 },
1672 { "cong", 8773 },
1673 { "asymp", 8776 },
1674 { "ne", 8800 },
1675 { "equiv", 8801 },
1676 { "le", 8804 },
1677 { "ge", 8805 },
1678 { "sub", 8834 },
1679 { "sup", 8835 },
1680 { "nsub", 8836 },
1681 { "sube", 8838 },
1682 { "supe", 8839 },
1683 { "oplus", 8853 },
1684 { "otimes", 8855 },
1685 { "perp", 8869 },
1686 { "sdot", 8901 },
1687 { "loz", 9674 },
1688 { "lceil", 8968 },
1689 { "rceil", 8969 },
1690 { "lfloor", 8970 },
1691 { "rfloor", 8971 },
1692 { "lang", 9001 },
1693 { "rang", 9002 },
1694 { "larr", 8592 },
1695 { "uarr", 8593 },
1696 { "rarr", 8594 },
1697 { "darr", 8595 },
1698 { "harr", 8596 },
1699 { "crarr", 8629 },
1700 { "lArr", 8656 },
1701 { "uArr", 8657 },
1702 { "rArr", 8658 },
1703 { "dArr", 8659 },
1704 { "hArr", 8660 },
1705 { "bull", 8226 },
1706 { "hellip", 8230 },
1707 { "prime", 8242 },
1708 { "oline", 8254 },
1709 { "frasl", 8260 },
1710 { "weierp", 8472 },
1711 { "image", 8465 },
1712 { "real", 8476 },
1713 { "trade", 8482 },
1714 { "euro", 8364 },
1715 { "alefsym", 8501 },
1716 { "spades", 9824 },
1717 { "clubs", 9827 },
1718 { "hearts", 9829 },
1719 { "diams", 9830 },
1720 { "ensp", 8194 },
1721 { "emsp", 8195 },
1722 { "thinsp", 8201 },
1723 { "zwnj", 8204 },
1724 { "zwj", 8205 },
1725 { "lrm", 8206 },
1726 { "rlm", 8207 },
1727 { "ndash", 8211 },
1728 { "mdash", 8212 },
1729 { "lsquo", 8216 },
1730 { "rsquo", 8217 },
1731 { "sbquo", 8218 },
1732 { "ldquo", 8220 },
1733 { "rdquo", 8221 },
1734 { "bdquo", 8222 },
1735 { "dagger", 8224 },
1736 { "Dagger", 8225 },
1737 { "permil", 8240 },
1738 { "lsaquo", 8249 },
1739 { "rsaquo", 8250 },
1740 { NULL, 0 }
1742 gint i;
1744 g_return_val_if_fail( NULL != htmlname, 0 );
1746 if( '\0' == *htmlname )
1747 return 0;
1749 if( '#' == *htmlname ) {
1750 const gchar *iter = htmlname;
1751 gunichar c = 0;
1752 if( 0 > len )
1753 i = 7;
1754 else
1755 i = len - 1;
1756 iter++;
1757 while( isdigit( *iter ) && ( 0 <= i ) ) {
1758 c = c * 10 + (*iter - '0');
1759 iter++;
1760 i--;
1763 if( 0 >= len ) {
1764 if( '\0' == *iter )
1765 return c;
1767 else if( 0 == i )
1768 return c;
1770 return 0;
1774 if( 0 > len ) {
1775 for( i = 0; NULL != html2utf8_table[ i ].str; i++ )
1776 if( 0 == strcmp( htmlname, html2utf8_table[ i ].str ) )
1777 return html2utf8_table[ i ].utf8;
1779 else if( 0 < len ) {
1780 for( i = 0; NULL != html2utf8_table[ i ].str; i++ )
1781 if( 0 == strncmp( htmlname, html2utf8_table[ i ].str, len ) )
1782 return html2utf8_table[ i ].utf8;
1785 return 0xFFFC;
1789 * Convert a string containing HTML encoded unichars to valid UTF-8.
1791 static gchar *htmlstr2utf8( const gchar* str )
1793 const gchar *amp_pos, *colon_pos, *copy_pos;
1794 gunichar uni = 0;
1795 GString *result;
1796 gsize len;
1798 if( NULL == str )
1799 return NULL;
1801 result = g_string_new( NULL );
1802 colon_pos = str;
1803 copy_pos = colon_pos;
1805 do {
1806 amp_pos = strchr( colon_pos, '&' );
1807 if( NULL == amp_pos )
1808 break;
1809 colon_pos = amp_pos;
1811 len = 0;
1812 while( (';' != *colon_pos)
1813 && ('\0' != *colon_pos) )
1815 colon_pos++;
1816 len++;
1819 if( (9 > len) && (2 < len) ) {
1820 uni = htmlname2unichar( amp_pos + 1, colon_pos - amp_pos - 1 );
1821 if( (0 != uni) && (TRUE == g_unichar_validate( uni )) ) {
1822 g_string_append_len( result, copy_pos, amp_pos - copy_pos );
1823 colon_pos++;
1824 copy_pos = colon_pos;
1825 g_string_append_unichar( result, uni );
1828 amp_pos = colon_pos;
1830 while( NULL != amp_pos );
1832 if( NULL != copy_pos )
1833 g_string_append( result, copy_pos );
1835 return g_string_free( result, FALSE );
1838 static gchar * strip_tags(gchar *html)
1840 gsize i = 0,j=0;
1841 unsigned depth = 0;
1842 while(html[i] != '\0') {
1843 if(html[i] == '<') depth++;
1844 else if(html[i] == '>') depth--;
1845 else if(depth == 0) {
1846 html[j] = html[i];
1847 j++;
1849 i++;
1851 html[j] = '\0';
1852 return html;
1855 gchar * meta_data_get_text_from_html(const MetaData *data)
1857 gchar *retv;
1858 g_assert(meta_data_is_html(data));
1859 retv = htmlstr2utf8((gchar *)data->content);
1860 /* need to strip tags */
1861 retv = strip_tags(retv);
1862 return retv;
1864 gboolean meta_data_is_raw(const MetaData *data)
1866 return data->content_type == META_DATA_CONTENT_RAW;
1869 const guchar * meta_data_get_raw(const MetaData *data, gsize *length)
1871 g_assert(meta_data_is_raw(data));
1872 if(length)
1873 *length = data->size;
1874 return (guchar *)data->content;
1876 gboolean meta_data_is_text_vector(const MetaData *data)
1878 return data->content_type == META_DATA_CONTENT_TEXT_VECTOR;
1880 const gchar ** meta_data_get_text_vector(const MetaData *data)
1882 g_assert(meta_data_is_text_vector(data));
1883 return (const gchar **)data->content;
1886 gboolean meta_data_is_text_list(const MetaData *data)
1888 return data->content_type == META_DATA_CONTENT_TEXT_LIST;
1890 const GList *meta_data_get_text_list(const MetaData *data)
1892 g_assert(meta_data_is_text_list(data));
1893 return (const GList *)data->content;
1896 * Plugin structure
1899 gmpcPlugin metadata_plug = {
1900 .name = N_("Metadata Handler"),
1901 .version = {1,1,1},
1902 .plugin_type = GMPC_INTERNALL,
1905 /* vim: set noexpandtab ts=4 sw=4 sts=4 tw=120: */