Extra debug
[gmpc.git] / src / MetaData / metadata.c
blobd37d536e2ae740e4e17d0377fc08f618f79cf292
1 /* Gnome Music Player Client (GMPC)
2 * Copyright (C) 2004-2011 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 "metadata-cache.h"
28 #include "preferences.h"
30 #include "gmpc_easy_download.h"
32 #define LOG_DOMAIN "MetaData"
34 #include <glyr/glyr.h>
35 #include <glyr/cache.h>
37 #define LOG_SUBCLASS "glyros"
38 #define LOG_COVER_NAME "fetch-art-album"
39 #define LOG_ARTIST_ART "fetch-art-artist"
40 #define LOG_SIMILIAR_ARTIST "fetch-similiar-artist"
41 #define LOG_SIMILIAR_SONG "fetch-similiar-song"
42 #define LOG_SIMILIAR_GENRE "fetch-similiar-genre"
43 #define LOG_ARTIST_TXT "fetch-biography-artist"
44 #define LOG_SONG_TXT "fetch-lyrics"
45 #define LOG_GUITARTABS "fetch-guitartabs"
46 #define LOG_ALBUM_TXT "fetch-album-txt"
48 // other
49 #define LOG_FUZZYNESS "fuzzyness"
50 #define LOG_CMINSIZE "cminsize"
51 #define LOG_CMAXSIZE "cmaxsize"
52 #define LOG_MSIMILIARTIST "msimiliartist"
53 #define LOG_MSIMILISONG "msimilisong"
54 #define LOG_QSRATIO "qsratio"
55 #define LOG_PARALLEL "parallel"
56 #define LOG_USERAGENT "useragent"
57 #define LOG_FROM "from"
58 int meta_num_plugins=0;
59 gmpcPluginParent **meta_plugins = NULL;
60 static void meta_data_sort_plugins(void);
61 GList *process_queue = NULL;
64 /**
65 * GLYR
68 static GAsyncQueue *gaq = NULL;
69 static GAsyncQueue *return_queue = NULL;
70 const char *plug_name = "glyr";
71 static GlyrDatabase *db = NULL;
74 /**
75 * This queue is used to send replies back.
77 //GQueue *meta_results = NULL;
80 /**
81 * Structure holding a metadata query */
82 typedef struct {
83 gboolean quit;
84 /* unique id for the query (unused)*/
85 guint id;
86 /* The callback to call when the query is done, or NULL */
87 MetaDataCallback callback;
88 /* Callback user_data pointer*/
89 gpointer data;
90 /* The song the data is queries for */
91 mpd_Song *song;
92 /* The song modified by the search system.
93 * This does albumartist, tag cleanup etc */
94 mpd_Song *edited;
95 /* The type of metadata */
96 MetaDataType type;
97 /* Result */
98 MetaDataResult result;
99 /* The actual result data */
100 MetaData *met;
101 /* The index of the plugin being queried */
102 int index;
103 int do_rename;
104 int rename_done;
105 #if 0
106 /* List with temporary result from plugin index */
107 GList *list;
108 /* The current position in the list */
109 GList *iter;
110 #endif
111 } meta_thread_data;
113 gboolean meta_compare_func(meta_thread_data *mt1, meta_thread_data *mt2);
114 //static gboolean meta_data_handle_results(void);
118 static gboolean meta_data_handler_data_match(meta_thread_data *data, gpointer data2);
120 mpd_Song *rewrite_mpd_song(mpd_Song *tsong, MetaDataType type)
122 mpd_Song *edited = NULL;
123 /* If it is not a mpd got song */
124 if(tsong->file == NULL )
126 if(type&(META_ALBUM_ART|META_ALBUM_TXT) && tsong->artist && tsong->album)
128 MpdData *data2 = NULL;
129 mpd_database_search_start(connection, TRUE);
130 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ARTIST, tsong->artist);
131 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM, tsong->album);
132 data2 = mpd_database_search_commit(connection);
133 if(data2)
135 edited = data2->song;
136 data2->song = NULL;
137 mpd_data_free(data2);
139 else if(mpd_server_tag_supported(connection,MPD_TAG_ITEM_ALBUM_ARTIST) && tsong->albumartist)
141 mpd_database_search_start(connection, TRUE);
142 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM_ARTIST, tsong->albumartist);
143 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM, tsong->album);
144 data2 = mpd_database_search_commit(connection);
145 if(data2)
147 edited = data2->song;
148 data2->song = NULL;
149 mpd_data_free(data2);
155 if(type&(META_ARTIST_ART|META_ARTIST_TXT) && tsong->artist)
157 MpdData *data2 = NULL;
158 mpd_database_search_start(connection, TRUE);
159 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ARTIST, tsong->artist);
160 data2 = mpd_database_search_commit(connection);
161 if(data2)
163 edited = data2->song;
164 data2->song = NULL;
165 mpd_data_free(data2);
169 if(!edited)
170 edited = mpd_songDup(tsong);
173 * Collections detection
174 * Only do this for album related queries.
176 if(type&(META_ALBUM_ART|META_ALBUM_TXT))
178 if(edited->albumartist)
180 if(edited->artist)
181 g_free(edited->artist);
182 edited->artist = g_strdup(edited->albumartist);
185 else if(edited->album && edited->file)
187 int i=0;
188 MpdData *data2;
189 char *dir = g_path_get_dirname(edited->file);
190 mpd_database_search_start(connection,TRUE);
191 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM, edited->album);
194 data2 = mpd_database_search_commit(connection);
195 if(data2)
197 for(i=0;data2; data2 = mpd_data_get_next(data2))
199 if(strncasecmp(data2->song->file, dir, strlen(dir))==0)
201 /* Check for NULL pointers */
202 if(data2->song->artist && edited->artist)
204 if(strcmp(data2->song->artist, edited->artist))
205 i++;
210 if(i >=3)
212 if(edited->artist)
213 g_free(edited->artist);
214 edited->artist = g_strdup("Various Artists");
216 g_free(dir);
220 * Artist renaming, Clapton, Eric -> Eric Clapton
222 if(edited->artist && cfg_get_single_value_as_int_with_default(config, "metadata", "rename", FALSE))
224 gchar **str = g_strsplit(edited->artist, ",", 2);
226 if(str[0] && str[1]) {
227 g_free(edited->artist);
228 edited->artist = g_strdup_printf("%s %s", g_strstrip(str[1]), g_strstrip(str[0]));
230 g_strfreev(str);
231 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "string converted to: '%s'", edited->artist);
235 * Sanitize album name and so (remove () (
237 if(cfg_get_single_value_as_int_with_default(config, "metadata", "sanitize", TRUE))
239 if(edited->album)
241 int i,j=0,depth=0;
242 int length;
243 char *album = edited->album;
244 edited->album = g_malloc0((strlen(album)+1)*sizeof(char));
245 length = strlen(album);
246 for(i=0;i< length;i++)
248 if(album[i] == '(') depth++;
249 else if (album[i] == ')')depth--;
250 else if (depth == 0) {
251 edited->album[j] = album[i];
252 j++;
255 g_free(album);
256 /* Remove trailing and leading spaces */
257 edited->album = g_strstrip(edited->album);
259 if(edited->title)
261 int i,j=0,depth=0;
262 char *title = edited->title;
263 int length;
264 edited->title = g_malloc0((strlen(title)+1)*sizeof(char));
265 length = strlen(title);
266 for(i=0;i< length;i++)
268 if(title[i] == '(') depth++;
269 else if (title[i] == ')')depth--;
270 else if (depth == 0) {
271 edited->title[j] = title[i];
272 j++;
275 g_free(title);
276 /* Remove trailing and leading spaces */
277 edited->title = g_strstrip(edited->title);
282 return edited;
285 * If the metadata thread managed to handle a result this function
286 * Will call the callback (from the main (gtk) thread)
288 static gboolean glyr_return_queue(void *user_data)
291 meta_thread_data *mtd = (meta_thread_data*)g_async_queue_try_pop(return_queue);
292 if(mtd)
294 printf("%s: results\n", __FUNCTION__);
296 gmpc_meta_watcher_data_changed(gmw,mtd->song, (mtd->type)&META_QUERY_DATA_TYPES, mtd->result,mtd->met);
297 if(mtd->callback)
299 mtd->callback(mtd->song, mtd->result, mtd->met, mtd->data);
302 /* Free any possible plugin results */
303 /* if(mtd->list){
304 g_list_foreach(mtd->list, (GFunc)meta_data_free, NULL);
305 g_list_free(mtd->list);
307 */ /* Free the result data */
308 if(mtd->met)
309 meta_data_free(mtd->met);
310 /* Free the copie and edited version of the songs */
311 if(mtd->song)
312 mpd_freeSong(mtd->song);
313 if(mtd->edited)
314 mpd_freeSong(mtd->edited);
316 /* Free the Request struct */
317 g_free(mtd);
318 return true;
320 return false;
323 static MetaDataContentType setup_glyr_query(GlyrQuery *query,
324 const meta_thread_data *mtd)
326 MetaDataContentType content_type = META_DATA_CONTENT_RAW;
328 /* Force UTF 8 */
329 glyr_opt_force_utf8(query, TRUE);
331 glyr_opt_parallel(query, 4);
333 /* set metadata */
334 glyr_opt_artist(query,(char*)mtd->song->artist);
335 glyr_opt_album (query,(char*)mtd->song->album);
336 glyr_opt_title (query,(char*)mtd->song->title);
338 /* set default type */
339 glyr_opt_type(query, GLYR_GET_UNSURE);
341 if(mtd->type == META_ARTIST_ART )
343 glyr_opt_type(query, GLYR_GET_ARTIST_PHOTOS);
344 content_type = META_DATA_CONTENT_RAW;
346 else if (mtd->type == META_BACKDROP_ART)
348 glyr_opt_type(query, GLYR_GET_BACKDROPS);
349 content_type = META_DATA_CONTENT_RAW;
351 else if(mtd->type == META_ARTIST_TXT)
353 glyr_opt_lang_aware_only(query,TRUE);
354 glyr_opt_type(query, GLYR_GET_ARTISTBIO);
355 content_type = META_DATA_CONTENT_TEXT;
357 else if(mtd->type == META_ARTIST_SIMILAR)
359 glyr_opt_type(query, GLYR_GET_SIMILIAR_ARTISTS);
360 // cfg_* is no longer thread safe
361 glyr_opt_number(query,20);
362 content_type = META_DATA_CONTENT_TEXT;
364 else if(mtd->type == META_ALBUM_ART &&
365 mtd->song->album != NULL)
367 glyr_opt_type(query, GLYR_GET_COVERART);
368 content_type = META_DATA_CONTENT_RAW;
370 else if(mtd->type == META_ALBUM_TXT &&
371 mtd->song->album != NULL)
373 glyr_opt_type(query, GLYR_GET_ALBUM_REVIEW);
374 content_type = META_DATA_CONTENT_TEXT;
376 else if(mtd->type == META_SONG_TXT &&
377 mtd->song->title != NULL)
379 glyr_opt_type(query, GLYR_GET_LYRICS);
380 content_type = META_DATA_CONTENT_TEXT;
382 else if(mtd->type == META_SONG_SIMILAR &&
383 mtd->song->title != NULL)
385 glyr_opt_type(query, GLYR_GET_SIMILIAR_SONGS);
386 // cfg_* is no longer thread safe
387 glyr_opt_number(query,20);
389 else if (mtd->type == META_SONG_GUITAR_TAB &&
390 mtd->song->title)
392 glyr_opt_type(query, GLYR_GET_GUITARTABS);
393 content_type = META_DATA_CONTENT_TEXT;
395 else {
396 g_warning("Unsupported metadata type, or insufficient info");
398 return content_type;
400 static MetaData * glyr_get_similiar_song_names(GlyrMemCache * cache)
402 MetaData * mtd = NULL;
403 while(cache != NULL)
405 if(cache->data != NULL)
407 gchar ** split = g_strsplit(cache->data,"\n",0);
408 if(split != NULL && split[0] != NULL)
410 gchar * buffer;
411 if(!mtd) {
412 mtd = meta_data_new();
413 mtd->type = META_SONG_SIMILAR;
414 mtd->plugin_name = plug_name;
415 mtd->content_type = META_DATA_CONTENT_TEXT_LIST;
416 mtd->size = 0;
419 buffer = g_strdup_printf("%s::%s",split[1],split[0]);
420 g_log(LOG_DOMAIN,G_LOG_LEVEL_DEBUG, "%s\n", buffer);
422 mtd->size++;
423 mtd->content = g_list_append((GList*) mtd->content, buffer);
424 g_strfreev(split);
427 cache = cache->next;
429 return mtd;
432 static MetaData * glyr_get_similiar_artist_names(GlyrMemCache * cache)
434 MetaData * mtd = NULL;
435 while(cache != NULL)
437 if(cache->data != NULL)
439 gchar ** split = g_strsplit(cache->data,"\n",0);
440 if(split != NULL)
442 if(!mtd) {
443 mtd = meta_data_new();
444 mtd->type = META_ARTIST_SIMILAR;
445 mtd->plugin_name = plug_name;
446 mtd->content_type = META_DATA_CONTENT_TEXT_LIST;
447 mtd->size = 0;
449 mtd->size++;
450 mtd->content = g_list_append((GList*) mtd->content,
451 g_strdup((char *)split[0]));
452 g_strfreev(split);
455 cache = cache->next;
457 return mtd;
460 * Convert GLYR result into gmpc result
462 static gboolean process_glyr_result(GlyrMemCache *cache,
463 MetaDataContentType content_type,
464 meta_thread_data *mtd)
466 gboolean retv = FALSE;
467 mtd->result = META_DATA_UNAVAILABLE;
468 mtd->met = NULL;
469 if(cache == NULL) return retv;
470 if(cache->rating >= 0)
472 if(mtd->type == META_ARTIST_SIMILAR)
474 MetaData * cont = glyr_get_similiar_artist_names(cache);
475 if(cont != NULL)
477 (mtd->met) = cont;
478 mtd->result = META_DATA_AVAILABLE;
479 retv = TRUE;
482 else if (mtd->type == META_SONG_SIMILAR)
484 MetaData * cont;
485 cont = glyr_get_similiar_song_names(cache);
486 if (cont != NULL)
488 (mtd->met) = cont;
489 mtd->result = META_DATA_AVAILABLE;
490 retv = TRUE;
493 else
495 (mtd->met) = meta_data_new();
496 (mtd->met)->type = mtd->type;
497 (mtd->met)->plugin_name = plug_name;
498 (mtd->met)->content_type = content_type;
500 (mtd->met)->content = g_malloc0(cache->size);
501 memcpy((mtd->met)->content, cache->data, cache->size);
502 (mtd->met)->size = cache->size;
503 mtd->result = META_DATA_AVAILABLE;
504 // found something.
505 retv = TRUE;
507 }else {
508 // Explicitely not found.
509 retv = TRUE;
511 return retv;
514 static GlyrQuery *glyr_exit_handle = NULL;
515 static GMutex *exit_handle_lock = NULL;
518 * Thread that does the GLYR requests
520 void glyr_fetcher_thread(void *user_data)
522 void *d;
523 GlyrQuery query;
525 g_mutex_lock(exit_handle_lock);
526 while((d = g_async_queue_pop(gaq)))
528 GLYR_ERROR err = GLYRE_OK;
529 MetaDataContentType content_type = META_DATA_CONTENT_RAW;
530 meta_thread_data *mtd = (meta_thread_data*)d;
531 GlyrMemCache *cache = NULL;
533 // Check if this is the quit command.
534 if(mtd->quit) {
535 printf("Quitting....");
536 g_mutex_unlock(exit_handle_lock);
537 /* Free the Request struct */
538 g_free(mtd);
539 return;
542 glyr_exit_handle = &query;
543 g_mutex_unlock(exit_handle_lock);
545 printf("new style query\n");
547 glyr_query_init(&query);
549 if(((mtd->type)&META_QUERY_NO_CACHE) == META_QUERY_NO_CACHE)
551 printf("Disable cache\n");
552 glyr_opt_from(&query, "all;-local");
553 // Remove cache request.
554 mtd->type&=META_QUERY_DATA_TYPES;
557 /* Set some random settings */
558 glyr_opt_verbosity(&query,2);
560 /* Tell libglyr to automatically lookup before searching the web */
561 glyr_opt_lookup_db(&query, db);
563 /* Also tell it to write newly found items to the db */
564 glyr_opt_db_autowrite(&query, TRUE);
566 /* */
567 content_type = setup_glyr_query(&query, mtd);
569 /* get metadata */
570 cache = glyr_get(&query,&err,NULL);
572 if(cache == NULL){
573 // Set dummy entry in cache, so we know
574 // we searched for this before.
575 cache = glyr_cache_new();
576 glyr_cache_set_data(cache, g_strdup("GMPC Dummy data"), -1);
577 cache->dsrc = g_strdup("GMPC dummy URL");
578 cache->rating = -1;
580 glyr_db_insert(db,&query, cache);
581 printf("Cache is Empty\n");
582 // Set unavailable
583 mtd->result = META_DATA_UNAVAILABLE;
584 }else{
585 process_glyr_result(cache,content_type, mtd);
587 // Cleanup
588 if(cache)glyr_free_list(cache);
590 // Clear the query, and lock the handle again.
591 g_mutex_lock(exit_handle_lock);
592 glyr_exit_handle = NULL;
593 glyr_query_destroy(&query);
595 // Push back result, and tell idle handle to handle it.
596 g_async_queue_push(return_queue, mtd);
597 // invalidate pointer.
598 mtd = NULL;
599 // Schedule the result thread in idle time.
600 g_idle_add(glyr_return_queue, NULL);
605 * Initialize
607 GThread *gaq_fetcher_thread = NULL;
608 void meta_data_init(void)
610 gchar *url;
612 /* Is this function thread safe? */
613 url = gmpc_get_covers_path("");
615 exit_handle_lock = g_mutex_new();
616 /* Initialize..*/
617 glyr_init();
618 db = glyr_db_init(url);
619 g_free(url);
622 gaq = g_async_queue_new();
623 return_queue = g_async_queue_new();
624 gaq_fetcher_thread = g_thread_create(glyr_fetcher_thread, NULL, TRUE, NULL);
628 void meta_data_add_plugin(gmpcPluginParent *plug)
630 g_assert(plug != NULL);
632 meta_num_plugins++;
633 meta_plugins = g_realloc(meta_plugins,(meta_num_plugins+1)*sizeof(gmpcPluginParent **));
634 meta_plugins[meta_num_plugins-1] = plug;
635 meta_plugins[meta_num_plugins] = NULL;
636 meta_data_sort_plugins();
639 static void meta_data_sort_plugins(void)
641 int i;
642 int changed = FALSE;
643 do{
644 changed=0;
645 for(i=0; i< (meta_num_plugins-1);i++)
647 if(gmpc_plugin_metadata_get_priority(meta_plugins[i]) > gmpc_plugin_metadata_get_priority(meta_plugins[i+1]))
649 gmpcPluginParent *temp = meta_plugins[i];
650 changed=1;
651 meta_plugins[i] = meta_plugins[i+1];
652 meta_plugins[i+1] = temp;
655 }while(changed);
658 static gboolean meta_data_check_plugin_changed_message(gpointer data)
660 playlist3_show_error_message(_("A new metadata plugin was added, gmpc has purged all failed hits from the cache"), ERROR_INFO);
661 return FALSE;
663 void meta_data_check_plugin_changed(void)
665 int old_amount= cfg_get_single_value_as_int_with_default(config, "metadata", "num_plugins", 0);
666 if(old_amount < meta_num_plugins)
668 gtk_init_add(meta_data_check_plugin_changed_message, NULL);
669 //metadata_cache_cleanup();
671 if(old_amount != meta_num_plugins)
673 cfg_set_single_value_as_int(config, "metadata", "num_plugins", meta_num_plugins);
677 * TODO: Can we guarantee that all the downloads are stopped?
679 void meta_data_destroy(void)
681 meta_thread_data *mtd = NULL;
682 INIT_TIC_TAC();
683 #if 0
684 if(process_queue) {
685 GList *iter;
686 /* Iterate through the list and destroy all entries */
687 for(iter = g_list_first(process_queue); iter; iter = iter->next)
689 mtd = iter->data;
691 /* Free any possible plugin results */
692 g_list_foreach(mtd->list, (GFunc)meta_data_free, NULL);
693 g_list_free(mtd->list);
694 mtd->list = mtd->iter = NULL;
695 /* Destroy the result */
696 if(mtd->met)
697 meta_data_free(mtd->met);
698 /* destroy the copied and modified song */
699 if(mtd->song)
700 mpd_freeSong(mtd->song);
701 if(mtd->edited)
702 mpd_freeSong(mtd->edited);
704 /* Free the Request struct */
705 g_free(mtd);
707 g_list_free(process_queue);
708 process_queue = NULL;
711 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG,"Done..");
712 /* Close the cover database */
713 TOC("test")
714 metadata_cache_destroy();
715 TOC("Config saved")
716 #endif
718 * Clear the request queue, and tell thread to quit
720 g_async_queue_lock(gaq);
721 while((mtd = g_async_queue_try_pop_unlocked(gaq))){
722 /* Free any possible plugin results */
724 if(mtd->list){
725 g_list_foreach(mtd->list, (GFunc)meta_data_free, NULL);
726 g_list_free(mtd->list);
729 */ /* Free the result data */
730 if(mtd->met)
731 meta_data_free(mtd->met);
732 /* Free the copie and edited version of the songs */
733 if(mtd->song)
734 mpd_freeSong(mtd->song);
735 if(mtd->edited)
736 mpd_freeSong(mtd->edited);
738 /* Free the Request struct */
739 g_free(mtd);
741 mtd = g_malloc0(sizeof(*mtd));
742 mtd->quit = TRUE;
743 g_async_queue_push_unlocked(gaq, mtd);
744 mtd = NULL;
745 g_async_queue_unlock(gaq);
746 // add lock?
747 g_mutex_lock(exit_handle_lock);
748 if(glyr_exit_handle) {
749 printf("Sending quit signal\n");
750 glyr_signal_exit(glyr_exit_handle);
752 g_mutex_unlock(exit_handle_lock);
754 printf("Waiting for glyr to finish.....\n");
755 g_thread_join(gaq_fetcher_thread);
756 g_mutex_free(exit_handle_lock);
758 glyr_db_destroy(db);
759 glyr_cleanup();
761 * Wait for thread to quit
763 g_async_queue_lock(return_queue);
764 while((mtd = g_async_queue_try_pop_unlocked(return_queue))){
765 /* Free any possible plugin results */
766 /* if(mtd->list){
767 g_list_foreach(mtd->list, (GFunc)meta_data_free, NULL);
768 g_list_free(mtd->list);
770 */ /* Free the result data */
771 if(mtd->met)
772 meta_data_free(mtd->met);
773 /* Free the copie and edited version of the songs */
774 if(mtd->song)
775 mpd_freeSong(mtd->song);
776 if(mtd->edited)
777 mpd_freeSong(mtd->edited);
779 /* Free the Request struct */
780 g_free(mtd);
782 g_async_queue_unlock(return_queue);
783 g_async_queue_unref(gaq);
784 g_async_queue_unref(return_queue);
787 gboolean meta_compare_func(meta_thread_data *mt1, meta_thread_data *mt2)
789 if((mt1->type&META_QUERY_DATA_TYPES) != (mt2->type&META_QUERY_DATA_TYPES))
790 return TRUE;
791 if(!gmpc_meta_watcher_match_data(mt1->type&META_QUERY_DATA_TYPES, mt1->song, mt2->song))
793 return TRUE;
795 return FALSE;
799 * new things *
801 #if 0
802 static int counter = 0;
803 static gboolean process_itterate(void);
805 * This function actually propagets the result.
806 * It is called (during idle time) when a request is done.
807 * The queue might hold one or more items
810 static gboolean meta_data_handle_results(void)
812 meta_thread_data *data = NULL;
814 * Check if there are results to handle
815 * do this until the list is clear
817 while((data = g_queue_pop_tail(meta_results)))
819 /* Signal the result to the gmpc-meta-watcher object, any listening client is interrested in the result. */
820 gmpc_meta_watcher_data_changed(gmw,data->song, (data->type)&META_QUERY_DATA_TYPES, data->result,data->met);
821 /* If a specific callback is set, call that */
822 if(data->callback)
824 data->callback(data->song,data->result,data->met, data->data);
827 * Start cleaning up the meta_thead_data
829 /* Free any possible plugin results */
831 if(data->list){
832 g_list_foreach(data->list, (GFunc)meta_data_free, NULL);
833 g_list_free(data->list);
835 /* Free the result data */
836 if(data->met)
837 meta_data_free(data->met);
838 /* Free the copie and edited version of the songs */
839 if(data->song)
840 mpd_freeSong(data->song);
841 if(data->edited)
842 mpd_freeSong(data->edited);
844 /* Free the Request struct */
845 g_free(data);
847 /* Make the idle handler stop */
848 return FALSE;
852 * This function handles it when data needs to be downloaded (in the cache) for now that are images.
854 /* TODO REMOVE */
855 static void metadata_download_handler(const GEADAsyncHandler *handle, GEADStatus status, gpointer user_data)
857 meta_thread_data *d = user_data;
859 if(status == GEAD_PROGRESS) {
860 /* If we are still downloading, do nothing */
861 return;
863 counter --;
864 if(status == GEAD_DONE)
866 /* If success, start processing result */
867 /* Get filename where we need to store the result. */
868 gchar *filename = gmpc_get_metadata_filename(d->type&(~META_QUERY_NO_CACHE), d->edited, NULL);
869 goffset length;
870 /* Get the data we downloaded */
871 const gchar *data = gmpc_easy_handler_get_data(handle, &length);
872 GError *error = NULL;
873 /* Try to store the data in the file */
874 if(length > 0)
876 g_file_set_contents(filename, data, length, &error);
877 if(error == NULL)
879 MetaData *md = d->iter->data;
880 /* Set result successs */
881 d->result = META_DATA_AVAILABLE;
882 /* If success create a MetaData object */
883 /* Create Metadata */
884 d->met = meta_data_new();
885 d->met->type = md->type;
886 d->met->plugin_name = md->plugin_name;
887 d->met->content_type = META_DATA_CONTENT_URI;
888 d->met->content = filename;
889 d->met->size = -1;
890 /* Convert ownership of string to d->met */
891 filename = NULL;
892 /* Free any remaining results */
893 g_list_foreach(d->list,(GFunc) meta_data_free, NULL);
894 g_list_free(d->list);
895 /* Set to NULL */
896 d->list = NULL;
897 d->iter = NULL;
898 /* by setting index to the last, the process_itterate knows it should not look further */
899 d->index = meta_num_plugins;
900 /* Iterate the */
902 g_idle_add((GSourceFunc)process_itterate, NULL);
903 //process_itterate();
904 /* we trown it back, quit */
905 return;
909 if(error){
910 /* Ok we failed to store it, now we cleanup, and try the next entry. */
911 g_log(LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Failed to store file: %s: '%s'", filename, error->message);
912 g_error_free(error); error = NULL;
914 else
915 g_log(LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Failed to store file: %s: '%i'", gmpc_easy_handler_get_uri(handle),(int)length);
916 /* If we fail, clean up and try next one */
917 g_free(filename);
920 * It seems we somehow _failed_ to get data, lets try the next result
922 if(d->iter)
924 /* If there is a result entry, advanced it to the next entry.*/
925 d->iter = g_list_next(d->iter);
926 if(d->iter){
927 /* Get the MetaData belonging to this result */
928 MetaData *md = (MetaData *)d->iter->data;
929 /* If it is an uri, we probly want to make a local copy of it. */
930 if(meta_data_is_uri(md))
932 const char *path = meta_data_get_uri(md);// d->iter->data;
933 if(path && path[0] == '/') {
934 /* if it is allready a local copy, store that */
935 /* set the result as final result */
936 d->met = md;
937 /* Remove it from the result list, so it does not get deleted twice */
938 d->iter->data= NULL;
939 /* set result available */
940 d->result = META_DATA_AVAILABLE;
941 }else if(path) {
942 /* Setup a new async downloader */
944 GEADAsyncHandler *new_handle = gmpc_easy_async_downloader((const gchar *)path,
945 metadata_download_handler, d);
946 /* If it is a remote one, download it */
947 if(new_handle != NULL)
949 /* if it is a success nothing left todo until the result comes in */
950 counter++;
951 return;
954 }else {
955 /* if it is not something we need to download, se the result as the final result */
956 d->result = META_DATA_AVAILABLE;
957 d->met = md;
958 /* remove result from list */
959 d->iter->data= NULL;
964 /* There are no more items to query, or we have a hit.
965 * Cleanup the results by this plugin and advanced to the next
967 if(d->list){
968 g_list_foreach(d->list,(GFunc) meta_data_free, NULL);
969 g_list_free(d->list);
971 d->list = NULL;
972 d->iter = NULL;
973 /* if we have a result, skip the rest of the plugins */
974 if(d->result == META_DATA_AVAILABLE)
975 d->index = meta_num_plugins;
976 else
977 d->index++;
978 /* itterate the next step */
979 g_idle_add((GSourceFunc)process_itterate, NULL);
980 //process_itterate();
983 * this function handles the result from the plugins
985 static void result_itterate(GList *list, gpointer user_data)
987 MetaData *md=NULL;
988 meta_thread_data *d = (meta_thread_data *)user_data;
991 * This plugin didn't have a result
992 * Skip to next plugin,
993 * retry.
995 if(list == NULL){
996 d->index++;
997 /* retry */
999 g_idle_add((GSourceFunc)process_itterate, NULL);
1000 //process_itterate();
1001 return;
1005 * Plugin has a result, try the downloading the first, if that fails
1007 d->list = list;
1008 /* Set the first one as the current one */
1009 d->iter = g_list_first(list);
1011 /* Get the result of the first query */
1012 md = d->iter->data;
1013 /* if the first is an uri, make a local cache copy */
1014 if(meta_data_is_uri(md))
1016 const char *path = meta_data_get_uri(md);
1017 if(path && path[0] == '/') {
1018 /* if it is a local copy, just set the result as final result */
1019 d->result = META_DATA_AVAILABLE;
1020 d->met = md;
1021 d->iter->data= NULL;
1022 }else if (path){
1023 /* if it is a remote uri, download it */
1025 GEADAsyncHandler *handle = gmpc_easy_async_downloader(path, metadata_download_handler, d);
1026 if(handle != NULL)
1028 /* if download is a success wait for result */
1029 counter++;
1030 return;
1033 }else if (meta_data_is_raw(md))
1035 gchar *filename = gmpc_get_metadata_filename(d->type&(~META_QUERY_NO_CACHE), d->edited, NULL);
1036 g_file_set_contents(filename, md->content, md->size, NULL);
1037 g_free(md->content);
1038 md->size = -1;
1039 md->content_type = META_DATA_CONTENT_URI;
1040 md->content = filename;
1041 d->result = META_DATA_AVAILABLE;
1042 d->met = md;
1043 d->iter->data= NULL;
1044 }else {
1045 /* if it is not something to download, set the result as final */
1046 d->result = META_DATA_AVAILABLE;
1047 d->met = md;
1048 d->iter->data= NULL;
1051 /* Free the remaining results. */
1052 if(d->list)
1054 g_list_foreach(d->list, (GFunc)meta_data_free, NULL);
1055 g_list_free(d->list);
1057 d->list = NULL;
1058 d->iter = NULL;
1061 * If still no result, try next plugin
1063 if(d->result != META_DATA_AVAILABLE)
1065 d->index++;
1068 * If we have a result.
1069 * Make it not check other plugins
1071 else
1072 d->index = meta_num_plugins;
1073 /* Continue processing */
1075 g_idle_add((GSourceFunc)process_itterate,NULL);
1076 //process_itterate();
1079 * This functions processes the requests, one by one
1081 static gboolean process_itterate(void)
1083 int length1;
1085 meta_thread_data *d = NULL;
1086 /* If queue is empty, do nothing */
1087 if(process_queue == NULL){
1088 return FALSE;
1093 * Get the first entry in the queue (this is the active one, or will be the active request
1095 d = process_queue->data;
1097 * Check if there are still plugins left to process
1099 if(d->index < meta_num_plugins)
1102 * Before checking all the plugins, query cache
1103 * Except when user asks to bypass cache
1105 if(d->index == 0)
1107 /* if the user requested a bypass of the cache, don't query it */
1108 if(d->type&META_QUERY_NO_CACHE)
1110 d->result = META_DATA_FETCHING;
1111 } else {
1112 MetaData *met = NULL;
1113 d->result = meta_data_get_from_cache(d->edited,d->type&META_QUERY_DATA_TYPES, &met);
1114 if(d->result != META_DATA_FETCHING){
1115 /* Copy the result to the final result */
1116 d->met = met;
1117 met = NULL;
1119 if(met)meta_data_free(met);
1123 * If cache has no reesult, query plugin
1124 * Only query when the cache returns
1126 if(d->result == META_DATA_FETCHING)
1128 /* Get the current plugin to query */
1129 gmpcPluginParent *plug = meta_plugins[d->index];
1131 * Query plugins
1133 if(gmpc_plugin_get_enabled(plug))
1135 gmpc_plugin_metadata_query_metadata_list(plug,
1136 d->edited,
1137 d->type&META_QUERY_DATA_TYPES,
1138 result_itterate,
1139 (gpointer)d);
1140 return FALSE;
1142 else
1144 /* advance to the next plugin */
1145 d->index++;
1146 /* do the next itteration */
1147 //process_itterate();
1148 return TRUE;
1150 return FALSE;
1153 /* A hack to also query non modified named. Only used with rename option enabled */
1154 if(d->do_rename && d->result == META_DATA_FETCHING )
1156 mpd_Song *song = d->edited;
1157 d->edited = d->song;
1158 d->song = song;
1159 d->do_rename = FALSE;
1160 d->index = 0;
1161 d->rename_done = TRUE;
1162 return TRUE;
1164 /* revert changes */
1165 if(d->rename_done)
1167 mpd_Song *song = d->edited;
1168 d->edited = d->song;
1169 d->song = song;
1172 * If nothing found, set unavailable
1174 if(d->result == META_DATA_FETCHING ) {
1175 d->result = META_DATA_UNAVAILABLE;
1177 /* Create empty metadata object */
1178 if(d->met == NULL)
1180 d->met = meta_data_new();
1181 d->met->type = (d->type&META_QUERY_DATA_TYPES);
1182 d->met->content_type = META_DATA_CONTENT_EMPTY;
1183 d->met->content = NULL; d->met->size = -1;
1186 * Store result (or lack off)
1188 meta_data_set_cache_real(d->edited, d->result, d->met);
1189 if(d->edited->artist && d->song->artist)
1191 if(strcmp(d->edited->artist, d->song->artist)!=0)
1192 meta_data_set_cache_real(d->song, /*d->type&META_QUERY_DATA_TYPES,*/ d->result, d->met);
1195 * Remove from queue
1197 length1 = g_list_length(process_queue);
1198 /* Remove top */
1199 process_queue = g_list_delete_link(process_queue, process_queue);
1200 if(process_queue){
1201 GList *iter = g_list_first(process_queue);
1202 for(;iter;iter = g_list_next(iter))
1204 meta_thread_data *d2 = iter->data;
1205 if(!meta_compare_func(d,d2)){
1206 /* Remove from intput list */
1207 iter = process_queue = g_list_delete_link(process_queue, iter);
1208 if(d2->callback)
1210 /* If old fasion query, copy result and push to result-handling */
1211 d2->result = d->result;
1212 d2->met = meta_data_dup(d->met);
1213 /* put result back */
1214 g_queue_push_tail(meta_results, d2);
1215 }else{
1216 /* if there is no old-type callback, We can remove it completely */
1217 /* Free result */
1218 if(d2->met)
1219 meta_data_free(d2->met);
1220 /* free stored song copies */
1221 if(d2->edited)
1222 mpd_freeSong(d2->edited);
1223 if(d2->song)
1224 mpd_freeSong(d2->song);
1226 q_free(d2);
1234 * push on handle result
1236 g_queue_push_tail(meta_results, d);
1238 * Call resuult handler
1240 g_idle_add((GSourceFunc)meta_data_handle_results,NULL);
1243 * Next in queue
1246 if(process_queue){
1247 /* Make it be called again, if there are still items in the queue*/
1248 return TRUE;
1250 // g_idle_add((GSourceFunc)process_itterate, __FUNCTION__);
1251 //process_itterate();
1252 return FALSE;
1254 #endif
1256 * Function called by the "client"
1259 MetaDataResult meta_data_get_path(mpd_Song *tsong, MetaDataType type, MetaData **met,MetaDataCallback callback, gpointer data)
1261 static int test_id = 0;
1262 meta_thread_data *mtd = NULL;
1264 * unique id
1265 * Not needed, but can be usefull for debugging
1268 mtd = g_malloc0(sizeof(*mtd));
1269 mtd->id = ++test_id;
1270 /* Create a copy of the original song */
1271 mtd->song = mpd_songDup(tsong);
1272 /* Set the type */
1273 mtd->type = type;
1274 /* the callback */
1275 mtd->callback = callback;
1276 /* the callback data */
1277 mtd->data = data;
1278 /* start at the first plugin */
1279 mtd->index = 0;
1280 mtd->do_rename = cfg_get_single_value_as_int_with_default(config, "metadata", "rename", FALSE);
1281 mtd->rename_done = FALSE;
1282 /* Set that we are fetching */
1283 mtd->result = META_DATA_FETCHING;
1284 /* set result NULL */
1285 mtd->met = NULL;
1287 * If requested query the cache first
1289 if((type&META_QUERY_NO_CACHE) == 0)
1291 MetaDataContentType content_type = META_DATA_CONTENT_RAW;
1292 GlyrQuery query;
1293 GlyrMemCache * cache = NULL;
1295 glyr_query_init(&query);
1296 /* Set some random settings */
1297 glyr_opt_verbosity(&query,2);
1299 content_type = setup_glyr_query(&query, mtd);
1301 cache = glyr_db_lookup(db, &query);
1302 if(process_glyr_result(cache,content_type, mtd))
1304 // Cleanup
1305 if(cache)glyr_free_list(cache);
1306 glyr_query_destroy(&query);
1308 // Push back result, and tell idle handle to handle it.
1309 g_async_queue_push(return_queue, mtd);
1310 mtd = NULL;
1311 g_idle_add(glyr_return_queue, NULL);
1313 printf("Got from cache\n");
1314 return META_DATA_FETCHING;
1316 if(cache)glyr_free_list(cache);
1317 glyr_query_destroy(&query);
1321 g_async_queue_push(gaq, mtd);
1322 mtd = NULL;
1323 return META_DATA_FETCHING;
1324 #if 0
1325 // static int test_id = 0;
1326 MetaDataResult ret;
1327 meta_thread_data *mtd = NULL;
1328 mpd_Song *song =NULL;
1329 /* TODO: Validate request */
1331 g_assert(met != NULL);
1332 g_assert(*met == NULL);
1334 * If there is no song
1335 * return there is not metadata available;
1337 if(tsong == NULL)
1339 return META_DATA_UNAVAILABLE;
1342 if(type&META_QUERY_NO_CACHE)
1345 * If the users did request a bypass,
1346 * Don't Check cache for result.
1347 * But signal an update
1349 gmpc_meta_watcher_data_changed(gmw,tsong, (type)&META_QUERY_DATA_TYPES,META_DATA_FETCHING, NULL);
1350 if(callback)
1352 callback(song,META_DATA_FETCHING,NULL,data);
1354 ret = META_DATA_FETCHING;
1356 else
1358 ret = meta_data_get_from_cache(tsong, type&META_QUERY_DATA_TYPES, met);
1360 * If the data is know. (and doesn't need fectching)
1361 * call the callback and stop
1363 if(ret != META_DATA_FETCHING)
1365 return ret;
1367 if(*met) meta_data_free(*met);
1368 *met = NULL;
1371 mtd = g_malloc0(sizeof(*mtd));
1373 * Get the 'edited' version we prefer to use when requesting metadata
1375 mtd->edited = rewrite_mpd_song(tsong, type);
1377 /**
1378 * Query cache, but for changed artist name
1380 if((type&META_QUERY_NO_CACHE) == 0)
1382 /* Query the cache for the edited version of the cache, if no bypass is requested */
1383 ret = meta_data_get_from_cache(mtd->edited, typenclude/glyrrest&META_QUERY_DATA_TYPES, met);
1385 * If the data is know. (and doesn't need fectching)
1386 * call the callback and stop
1388 if(ret != META_DATA_FETCHING)
1390 /* store it under the original */
1391 meta_data_set_cache_real(tsong, ret,*met);
1392 /* Cleanup what we had so far */
1393 mpd_freeSong(mtd->edited);
1394 g_free(mtd);
1395 /* Steal result */
1396 return ret;
1398 /* If it is fetching free the result */
1399 if(*met) meta_data_free(*met);
1400 *met = NULL;
1405 * If no result, start a thread and start fetching the data from there
1410 * unique id
1411 * Not needed, but can be usefull for debugging
1413 mtd->id = ++test_id;
1414 /* Create a copy of the original song */
1415 mtd->song = mpd_songDup(tsong);
1416 /* Set the type */
1417 mtd->type = type;
1418 /* the callback */
1419 mtd->callback = callback;
1420 /* the callback data */
1421 mtd->data = data;
1422 /* start at the first plugin */
1423 mtd->index = 0;
1424 mtd->do_rename = cfg_get_single_value_as_int_with_default(config, "metadata", "rename", FALSE);
1425 mtd->rename_done = FALSE;
1426 /* Set that we are fetching */
1427 mtd->result = META_DATA_FETCHING;
1428 /* set result NULL */
1429 mtd->met = NULL;
1431 if(process_queue == NULL) {
1432 /* If queue is empty, add it, and call the processing function. */
1433 process_queue = g_list_append(process_queue, mtd);
1435 g_idle_add((GSourceFunc)process_itterate, NULL);
1436 //process_itterate();
1437 return META_DATA_FETCHING;
1439 /* if queue not empy, append itand do nothing else */
1440 process_queue = g_list_append(process_queue, mtd);
1442 return META_DATA_FETCHING;
1443 #endif
1446 static gchar * strip_invalid_chars(gchar *input)
1448 int i = 0;
1449 int length = 0;
1450 g_assert(input != NULL);
1451 length = strlen(input);
1452 if(input == NULL) return NULL;
1453 for(i=0;i<length;i++)
1455 switch(input[i])
1457 case ':':
1458 case '\\':
1459 case '/':
1460 case ';':
1461 case '*':
1462 case '?':
1463 case '\"':
1464 case '<':
1465 case '>':
1466 case '|':
1467 input[i] = ' ';
1468 default:
1469 break;
1472 return input;
1475 * Helper function for storing metadata
1477 gchar * gmpc_get_metadata_filename(MetaDataType type, mpd_Song *song, char *ext)
1479 gchar *retv= NULL;
1480 /* home dir */
1481 const gchar *homedir = g_get_user_cache_dir();
1482 g_assert(song->artist != NULL);
1483 g_assert(type < META_QUERY_DATA_TYPES);
1486 GError *error = NULL;
1487 gchar *filename = NULL, *dirname = NULL;
1488 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);
1490 /* Convert it so the filesystem likes it */
1491 /* TODO: Add error checking */
1493 dirname = g_filename_from_utf8(song->artist, -1, NULL, NULL, NULL);
1495 if (g_get_charset (&charset))
1497 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Locale is utf-8, just copying");
1498 dirname = g_strdup(song->artist);
1499 }else{
1500 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Locale is %s converting to UTF-8", charset);
1501 dirname = g_convert_with_fallback (song->artist, -1,
1502 charset, "UTF-8","-", NULL, NULL, &error);
1505 if(dirname == NULL)
1507 const gchar *charset;
1508 g_get_charset (&charset);
1509 dirname = g_convert_with_fallback (song->artist, -1,
1510 charset, "UTF-8",(char *)"-", NULL, NULL, &error);
1512 //dirname = g_filename_from_utf8(song->artist,-1,NULL,NULL,&error);
1513 if(error) {
1514 g_log(LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Failed to convert %s to file encoding. '%s'", song->artist, error->message);
1515 g_error_free(error);
1516 if(dirname) g_free(dirname);
1517 dirname = g_strdup("invalid");
1519 dirname = strip_invalid_chars(dirname);
1520 retv = g_build_path(G_DIR_SEPARATOR_S, homedir,"gmpc","metadata", dirname,NULL);
1521 if(g_file_test(retv, G_FILE_TEST_EXISTS) == FALSE) {
1522 if(g_mkdir_with_parents(retv, 0755) < 0) {
1523 g_error("Failed to create: %s\n", retv);
1524 abort();
1527 if(!g_file_test(retv, G_FILE_TEST_IS_DIR)) {
1528 g_error("File exists but is not a directory: %s\n", retv);
1529 abort();
1531 g_free(retv);
1532 if(type&(META_ALBUM_ART|META_ALBUM_TXT)) {
1533 gchar *temp ;
1534 g_assert(song->album != NULL);
1535 temp =g_filename_from_utf8(song->album,-1,NULL,NULL,NULL);
1536 filename = g_strdup_printf("%s.%s", temp,extension);
1537 g_free(temp);
1538 }else if(type&META_ARTIST_ART){
1539 filename = g_strdup_printf("artist_IMAGE.%s", extension);
1540 }else if (type&META_ARTIST_TXT){
1541 filename = g_strdup_printf("artist_BIOGRAPHY.%s", extension);
1542 }else if (type&META_SONG_TXT) {
1543 gchar *temp ;
1544 g_assert(song->title != NULL);
1545 temp =g_filename_from_utf8(song->title,-1,NULL,NULL,NULL);
1546 filename = g_strdup_printf("%s_LYRIC.%s", temp,extension);
1547 g_free(temp);
1548 }else if (type&META_SONG_GUITAR_TAB) {
1549 gchar *temp ;
1550 g_assert(song->title != NULL);
1551 temp =g_filename_from_utf8(song->title,-1,NULL,NULL,NULL);
1552 filename = g_strdup_printf("%s_GUITAR_TAB.%s", temp,extension);
1553 g_free(temp);
1555 filename = strip_invalid_chars(filename);
1556 retv = g_build_path(G_DIR_SEPARATOR_S, homedir,"gmpc", "metadata", dirname,filename,NULL);
1557 if(filename) g_free(filename);
1558 if(dirname) g_free(dirname);
1560 return retv;
1562 static void metadata_pref_priority_changed(GtkCellRenderer *renderer, char *path, char *new_text, GtkListStore *store)
1564 GtkTreeIter iter;
1565 if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path))
1567 gmpcPluginParent *plug;
1568 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 0, &plug, -1);
1569 if(plug)
1571 gmpc_plugin_metadata_set_priority(plug,(gint)g_ascii_strtoull(new_text, NULL, 0));
1572 gtk_list_store_set(GTK_LIST_STORE(store), &iter, 2, gmpc_plugin_metadata_get_priority(plug),-1);
1573 meta_data_sort_plugins();
1578 * Get the enabled state directly from the plugin
1580 static void __column_data_func_enabled(GtkTreeViewColumn *column,
1581 GtkCellRenderer *cell,
1582 GtkTreeModel *model,
1583 GtkTreeIter *iter,
1584 gpointer data)
1586 gmpcPluginParent *plug;
1587 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, 0, &plug, -1);
1588 if(plug)
1590 gboolean active = gmpc_plugin_get_enabled(plug);
1591 g_object_set(G_OBJECT(cell), "active", active, NULL);
1595 * Set enabled
1597 static void __column_toggled_enabled(GtkCellRendererToggle *renderer,
1598 char *path,
1599 gpointer store)
1601 GtkTreeIter iter;
1602 if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path))
1604 gmpcPluginParent *plug;
1605 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 0, &plug, -1);
1606 if(plug)
1608 gboolean state = !gtk_cell_renderer_toggle_get_active(renderer);
1609 gmpc_plugin_set_enabled(plug,state);
1610 preferences_window_update();
1614 static void metadata_construct_pref_pane(GtkWidget *container)
1616 GtkObject *adjustment;
1617 int i = 0;
1618 GtkCellRenderer *renderer;
1619 GtkWidget *vbox, *sw;
1620 GtkWidget *treeview;
1621 GtkWidget *label = NULL;
1622 GtkListStore *store = gtk_list_store_new(3,
1623 G_TYPE_POINTER, /* The GmpcPlugin */
1624 G_TYPE_STRING, /* Name */
1625 G_TYPE_INT /* The priority */
1627 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
1628 2, GTK_SORT_ASCENDING);
1631 /* Create vbox */
1632 vbox = gtk_vbox_new(FALSE, 6);
1633 /* tree + container */
1634 sw = gtk_scrolled_window_new(NULL, NULL);
1635 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
1636 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1637 treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1638 gtk_container_add(GTK_CONTAINER(sw), treeview);
1641 /* enable column */
1642 renderer = gtk_cell_renderer_toggle_new();
1643 gtk_tree_view_insert_column_with_data_func(GTK_TREE_VIEW(treeview),
1645 "Enabled",
1646 renderer,
1647 (GtkTreeCellDataFunc)__column_data_func_enabled,
1648 NULL,
1649 NULL);
1650 g_object_set(G_OBJECT(renderer), "activatable", TRUE, NULL);
1651 g_signal_connect(G_OBJECT(renderer), "toggled" ,
1652 G_CALLBACK(__column_toggled_enabled), store);
1654 /* Build the columns */
1655 renderer = gtk_cell_renderer_text_new();
1656 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),
1658 "Name",
1659 renderer,
1660 "text", 1,
1661 NULL);
1662 renderer = gtk_cell_renderer_spin_new();
1664 adjustment = gtk_adjustment_new (0, 0, 100, 1, 0, 0);
1665 g_object_set(G_OBJECT(renderer), "editable", TRUE, NULL);
1666 g_object_set (renderer, "adjustment", adjustment, NULL);
1667 g_signal_connect(G_OBJECT(renderer), "edited", G_CALLBACK(metadata_pref_priority_changed), store);
1668 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),
1670 "Priority",
1671 renderer,
1672 "text", 2,
1673 NULL);
1676 /* Add the list to the vbox */
1677 gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
1679 gtk_container_add(GTK_CONTAINER(container), vbox);
1680 /* add plugins to list */
1681 for(i=0; i< meta_num_plugins;i++)
1683 GtkTreeIter iter;
1684 gtk_list_store_insert_with_values(store, &iter, -1,
1685 0, meta_plugins[i],
1686 1, gmpc_plugin_get_name(meta_plugins[i]),
1687 2, gmpc_plugin_metadata_get_priority(meta_plugins[i]),
1688 -1);
1691 label = gtk_label_new("Plugins are evaluated from low priority to high");
1692 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
1693 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1694 gtk_widget_show_all(container);
1696 static void metadata_destroy_pref_pane(GtkWidget *container)
1698 GtkWidget *child = gtk_bin_get_child(GTK_BIN(container));
1699 if(child)
1701 gtk_widget_destroy(child);
1706 * Get a list of urls, used by f.e. selector
1708 #if 0
1709 typedef struct MLQuery{
1710 int index;
1711 int cancel;
1712 void (*callback)(gpointer handle,const gchar *plugin_name, GList *list, gpointer data);
1713 gpointer userdata;
1714 MetaDataType type;
1715 mpd_Song *song;
1716 int calls;
1717 }MLQuery;
1719 static void metadata_get_list_itterate(GList *list, gpointer data);
1720 static gboolean metadata_get_list_itterate_idle(gpointer data)
1722 g_log("MetaData", G_LOG_LEVEL_DEBUG, "List itterate idle");
1723 metadata_get_list_itterate(NULL, data);
1724 return FALSE;
1727 static void metadata_get_list_itterate(GList *list, gpointer data)
1729 MLQuery *q = (MLQuery *)data;
1730 g_log("MetaData", G_LOG_LEVEL_DEBUG, "List itterate");
1731 q->calls--;
1732 if(q->cancel){
1733 if(list){
1734 g_list_foreach(list,(GFunc) meta_data_free, NULL);
1735 g_list_free(list);
1737 if(q == 0)
1739 mpd_freeSong(q->song);
1740 g_free(q);
1742 /* clean up */
1743 return;
1745 if(list) {
1746 gmpcPluginParent *plug = meta_plugins[q->index-1];
1747 q->callback(q,gmpc_plugin_get_name(plug), list,q->userdata);
1748 g_list_foreach(list,(GFunc) meta_data_free, NULL);
1749 g_list_free(list);
1751 if(q->index < meta_num_plugins)
1753 gmpcPluginParent *plug = meta_plugins[q->index];
1754 q->index++;
1755 if(gmpc_plugin_get_enabled(plug)){
1756 q->calls++;
1757 g_log("MetaData", G_LOG_LEVEL_DEBUG, "Query: %s", gmpc_plugin_get_name(plug));
1758 gmpc_plugin_metadata_query_metadata_list(plug, q->song, q->type&META_QUERY_DATA_TYPES,metadata_get_list_itterate, (gpointer)q);
1760 else g_idle_add(metadata_get_list_itterate_idle, q);
1761 return;
1763 /* indicate done, by calling with NULL handle */
1764 q->callback(q,NULL, NULL, q->userdata);
1766 mpd_freeSong(q->song);
1767 g_free(q);
1769 #endif
1770 void metadata_get_list_cancel(gpointer data)
1772 #if 0
1773 MLQuery *q = (MLQuery *)data;
1774 q->cancel = TRUE;
1775 #endif
1777 gpointer metadata_get_list(mpd_Song *song, MetaDataType type, void (*callback)(gpointer handle,const gchar *plugin_name, GList *list, gpointer data), gpointer data)
1779 callback(NULL, NULL, NULL, data);
1780 return NULL;
1781 #if 0
1782 MLQuery *q = g_malloc0(sizeof(*q));
1783 q->cancel =FALSE;
1784 q->index = 0;
1785 q->callback = callback;
1786 q->userdata = data;
1787 q->type = type;
1788 q->calls =1;
1790 * Create a copy, so song is guarantee to be valid during queries of plugins
1792 q->song = mpd_songDup(song);
1793 /* Check cache */
1795 MetaData *met = NULL;
1796 int retv = meta_data_get_from_cache(q->song, q->type,&met);
1797 g_log("MetaData", G_LOG_LEVEL_DEBUG, "Queried cache: %i",retv);
1798 if(retv == META_DATA_AVAILABLE)
1800 GList *list = g_list_append(NULL, met);
1802 g_log("MetaData", G_LOG_LEVEL_DEBUG, "Callback");
1803 q->callback(q, met->plugin_name,list, q->userdata);
1804 g_log("MetaData", G_LOG_LEVEL_DEBUG, "Cleanup");
1805 g_list_foreach(g_list_first(list),(GFunc) meta_data_free, NULL);
1806 g_list_free(list);
1807 g_log("MetaData", G_LOG_LEVEL_DEBUG, "Cleanup done");
1808 met = NULL;
1810 if(met)
1811 meta_data_free(met);
1814 g_log("MetaData", G_LOG_LEVEL_DEBUG, "Start first itteration idle");
1815 g_idle_add(metadata_get_list_itterate_idle, q);
1816 return q;
1817 #endif
1820 * MetaData
1822 MetaData *meta_data_new(void)
1824 /* Create a new structure completely filled with 0's */
1825 MetaData *retv = g_new0(MetaData, 1);
1826 retv->content_type = META_DATA_CONTENT_EMPTY;
1827 return retv;
1830 void meta_data_free(MetaData *data)
1832 if(data == NULL) return;
1833 if(data->content) {
1834 if(data->content_type == META_DATA_CONTENT_TEXT_VECTOR)
1836 g_strfreev(data->content);
1838 else if (data->content_type == META_DATA_CONTENT_TEXT_LIST)
1840 g_list_foreach((GList *)data->content, (GFunc)g_free, NULL);
1841 g_list_free((GList *)data->content);
1843 else if(data->content_type != META_DATA_CONTENT_EMPTY)
1844 g_free(data->content);
1845 data->content = NULL;
1846 data->size = 0;
1848 if(data->thumbnail_uri) g_free(data->thumbnail_uri);
1849 data->thumbnail_uri = NULL;
1850 g_free(data);
1853 MetaData *meta_data_dup(MetaData *data)
1855 MetaData *retv = meta_data_new();
1856 g_assert(data != NULL);
1857 /* Copy type of metadata */
1858 retv->type = data->type;
1859 /* Copy the type of the data */
1860 retv->content_type = data->content_type;
1861 /* Copy the name of the providing plugin. (const char * so only copy pointer ) */
1862 retv->plugin_name = data->plugin_name;
1863 /* copy the content */
1864 retv->size = data->size;
1865 if(retv->content_type == META_DATA_CONTENT_TEXT_VECTOR) {
1866 if(data->content) retv->content =(void *) g_strdupv((gchar **)data->content);
1868 /* raw data always needs a length */
1869 else if (data->content_type == META_DATA_CONTENT_RAW)
1871 if(data->size > 0 ) {
1872 retv->content = g_memdup(data->content, (guint)data->size);
1875 else if (data->content_type == META_DATA_CONTENT_TEXT_LIST)
1877 GList *list = NULL;
1878 GList *iter = g_list_first((GList *)(data->content));
1879 while((iter = g_list_next(iter)))
1881 list = g_list_append(list, g_strdup(iter->data));
1883 data->content =(void *) g_list_reverse(list);
1885 else if (data->content_type == META_DATA_CONTENT_EMPTY)
1887 retv->content = NULL; retv->size = 0;
1889 /* Text is NULL terminated */
1890 else
1892 retv->content = NULL;
1893 if(data->content){
1894 retv->content = g_strdup((gchar *)data->content);
1897 if(data->thumbnail_uri != NULL) {
1898 retv->thumbnail_uri = g_strdup(data->thumbnail_uri);
1900 return retv;
1902 MetaData *meta_data_dup_steal(MetaData *data)
1904 MetaData *retv = meta_data_new();
1905 g_assert(data != NULL);
1906 /* Copy type of metadata */
1907 retv->type = data->type;
1908 /* Copy the type of the data */
1909 retv->content_type = data->content_type;
1910 /* Copy the name of the providing plugin. (const char * so only copy pointer ) */
1911 retv->plugin_name = data->plugin_name;
1912 /* copy the content */
1913 retv->size = data->size;
1914 retv->content = data->content;
1915 data->size = 0;
1916 data->content = NULL;
1917 retv->thumbnail_uri = data->thumbnail_uri;
1918 data->thumbnail_uri = NULL;
1919 return retv;
1921 gboolean meta_data_is_empty(const MetaData *data)
1923 return data->content_type == META_DATA_CONTENT_EMPTY;
1925 gboolean meta_data_is_uri(const MetaData *data)
1927 return data->content_type == META_DATA_CONTENT_URI;
1929 const gchar * meta_data_get_uri(const MetaData *data)
1931 g_assert(meta_data_is_uri(data));
1932 return (const gchar *)data->content;
1934 void meta_data_set_uri(MetaData *data, const gchar *uri)
1936 g_assert(meta_data_is_uri(data));
1937 if(data->content) g_free(data->content);
1938 data->content = g_strdup(uri);
1940 void meta_data_set_raw(MetaData *item, guchar *data, gsize len)
1942 g_assert(meta_data_is_raw(item));
1943 if(item->content) g_free(item->content);
1944 item->content = g_memdup(data, len);
1945 item->size = len;
1947 void meta_data_set_raw_owned(MetaData *item, guchar **data, gsize *len)
1949 g_assert(meta_data_is_raw(item));
1950 if(item->content) g_free(item->content);
1951 item->content = *data;
1952 *data = NULL;
1953 item->size = *len;
1954 *len = 0;
1956 void meta_data_set_thumbnail_uri(MetaData *data, const gchar *uri)
1958 g_assert(meta_data_is_uri(data));
1959 if(data->thumbnail_uri) g_free(data->thumbnail_uri);
1960 data->thumbnail_uri = g_strdup(uri);
1962 const gchar * meta_data_get_thumbnail_uri(const MetaData *data)
1964 g_assert(meta_data_is_uri(data));
1965 /* Only valid for images. */
1966 g_assert((data->type&(META_ALBUM_ART|META_ARTIST_ART)) != 0);
1968 return (const gchar *)data->thumbnail_uri;
1971 gboolean meta_data_is_text(const MetaData *data)
1973 return data->content_type == META_DATA_CONTENT_TEXT;
1976 void meta_data_set_text(MetaData *data, const gchar *text)
1978 if(meta_data_is_text(data))
1980 if(data->content) g_free(data->content);
1981 data->content = g_strdup(text);
1982 data->size = -1;
1985 const gchar * meta_data_get_text(const MetaData *data)
1987 g_assert(meta_data_is_text(data));
1988 return (const gchar *)data->content;
1991 gboolean meta_data_is_html(const MetaData *data)
1993 return data->content_type == META_DATA_CONTENT_HTML;
1995 const gchar * meta_data_get_html(const MetaData *data)
1997 g_assert(meta_data_is_html(data));
1998 return (const gchar *)data->content;
2003 * Convert a html encoded token (like &amp; and &#030;
2004 * to the corresponding unichar.
2006 * On early falire (i.e. empty string) returns 0.
2007 * If the encoding is wrong, it returns the unicode error value 0xFFFC.
2009 static gunichar htmlname2unichar( const gchar* htmlname, gint len )
2011 // * can be optimized by sorting
2012 // * and checking str(n)cmp results
2013 static struct _TT {
2014 const gchar *str;
2015 gint utf8;
2017 html2utf8_table[] = {
2018 { "quot", 34 },
2019 { "amp", 38 },
2020 { "lt", 60 },
2021 { "gt", 62 },
2022 { "nbsp", 160 },
2023 { "iexcl", 161 },
2024 { "cent", 162 },
2025 { "pound", 163 },
2026 { "curren", 164 },
2027 { "yen", 165 },
2028 { "brvbar", 166 },
2029 { "sect", 167 },
2030 { "uml", 168 },
2031 { "copy", 169 },
2032 { "ordf", 170 },
2033 { "laquo", 171 },
2034 { "not", 172 },
2035 { "shy", 173 },
2036 { "reg", 174 },
2037 { "macr", 175 },
2038 { "deg", 176 },
2039 { "plusmn", 177 },
2040 { "sup2", 178 },
2041 { "sup3", 179 },
2042 { "acute", 180 },
2043 { "micro", 181 },
2044 { "para", 182 },
2045 { "middot", 183 },
2046 { "cedil", 184 },
2047 { "sup1", 185 },
2048 { "ordm", 186 },
2049 { "raquo", 187 },
2050 { "frac14", 188 },
2051 { "frac12", 189 },
2052 { "frac34", 190 },
2053 { "iquest", 191 },
2054 { "Agrave", 192 },
2055 { "Aacute", 193 },
2056 { "Acirc", 194 },
2057 { "Atilde", 195 },
2058 { "Auml", 196 },
2059 { "Aring", 197 },
2060 { "AElig", 198 },
2061 { "Ccedil", 199 },
2062 { "Egrave", 200 },
2063 { "Eacute", 201 },
2064 { "Ecirc", 202 },
2065 { "Euml", 203 },
2066 { "Igrave", 204 },
2067 { "Iacute", 205 },
2068 { "Icirc", 206 },
2069 { "Iuml", 207 },
2070 { "ETH", 208 },
2071 { "Ntilde", 209 },
2072 { "Ograve", 210 },
2073 { "Oacute", 211 },
2074 { "Ocirc", 212 },
2075 { "Otilde", 213 },
2076 { "Ouml", 214 },
2077 { "times", 215 },
2078 { "Oslash", 216 },
2079 { "Ugrave", 217 },
2080 { "Uacute", 218 },
2081 { "Ucirc", 219 },
2082 { "Uuml", 220 },
2083 { "Yacute", 221 },
2084 { "THORN", 222 },
2085 { "szlig", 223 },
2086 { "agrave", 224 },
2087 { "aacute", 225 },
2088 { "acirc", 226 },
2089 { "atilde", 227 },
2090 { "auml", 228 },
2091 { "aring", 229 },
2092 { "aelig", 230 },
2093 { "ccedil", 231 },
2094 { "egrave", 232 },
2095 { "eacute", 233 },
2096 { "ecirc", 234 },
2097 { "euml", 235 },
2098 { "igrave", 236 },
2099 { "iacute", 237 },
2100 { "icirc", 238 },
2101 { "iuml", 239 },
2102 { "eth", 240 },
2103 { "ntilde", 241 },
2104 { "ograve", 242 },
2105 { "oacute", 243 },
2106 { "ocirc", 244 },
2107 { "otilde", 245 },
2108 { "ouml", 246 },
2109 { "divide", 247 },
2110 { "oslash", 248 },
2111 { "ugrave", 249 },
2112 { "uacute", 250 },
2113 { "ucirc", 251 },
2114 { "uuml", 252 },
2115 { "yacute", 253 },
2116 { "thorn", 254 },
2117 { "yuml", 255 },
2118 { "Alpha", 913 },
2119 { "alpha", 945 },
2120 { "Beta", 914 },
2121 { "beta", 946 },
2122 { "Gamma", 915 },
2123 { "gamma", 947 },
2124 { "Delta", 916 },
2125 { "delta", 948 },
2126 { "Epsilon", 917 },
2127 { "epsilon", 949 },
2128 { "Zeta", 918 },
2129 { "zeta", 950 },
2130 { "Eta", 919 },
2131 { "eta", 951 },
2132 { "Theta", 920 },
2133 { "theta", 952 },
2134 { "Iota", 921 },
2135 { "iota", 953 },
2136 { "Kappa", 922 },
2137 { "kappa", 954 },
2138 { "Lambda", 923 },
2139 { "lambda", 955 },
2140 { "Mu", 924 },
2141 { "mu", 956 },
2142 { "Nu", 925 },
2143 { "nu", 957 },
2144 { "Xi", 926 },
2145 { "xi", 958 },
2146 { "Omicron", 927 },
2147 { "omicron", 959 },
2148 { "Pi", 928 },
2149 { "pi", 960 },
2150 { "Rho", 929 },
2151 { "rho", 961 },
2152 { "Sigma", 931 },
2153 { "sigmaf", 962 },
2154 { "sigma", 963 },
2155 { "Tau", 932 },
2156 { "tau", 964 },
2157 { "Upsilon", 933 },
2158 { "upsilon", 965 },
2159 { "Phi", 934 },
2160 { "phi", 966 },
2161 { "Chi", 935 },
2162 { "chi", 967 },
2163 { "Psi", 936 },
2164 { "psi", 968 },
2165 { "Omega", 937 },
2166 { "omega", 969 },
2167 { "thetasym", 977 },
2168 { "upsih", 978 },
2169 { "piv", 982 },
2170 { "forall", 8704 },
2171 { "part", 8706 },
2172 { "exist", 8707 },
2173 { "empty", 8709 },
2174 { "nabla", 8711 },
2175 { "isin", 8712 },
2176 { "notin", 8713 },
2177 { "ni", 8715 },
2178 { "prod", 8719 },
2179 { "sum", 8721 },
2180 { "minus", 8722 },
2181 { "lowast", 8727 },
2182 { "radic", 8730 },
2183 { "prop", 8733 },
2184 { "infin", 8734 },
2185 { "ang", 8736 },
2186 { "and", 8869 },
2187 { "or", 8870 },
2188 { "cap", 8745 },
2189 { "cup", 8746 },
2190 { "int", 8747 },
2191 { "there4", 8756 },
2192 { "sim", 8764 },
2193 { "cong", 8773 },
2194 { "asymp", 8776 },
2195 { "ne", 8800 },
2196 { "equiv", 8801 },
2197 { "le", 8804 },
2198 { "ge", 8805 },
2199 { "sub", 8834 },
2200 { "sup", 8835 },
2201 { "nsub", 8836 },
2202 { "sube", 8838 },
2203 { "supe", 8839 },
2204 { "oplus", 8853 },
2205 { "otimes", 8855 },
2206 { "perp", 8869 },
2207 { "sdot", 8901 },
2208 { "loz", 9674 },
2209 { "lceil", 8968 },
2210 { "rceil", 8969 },
2211 { "lfloor", 8970 },
2212 { "rfloor", 8971 },
2213 { "lang", 9001 },
2214 { "rang", 9002 },
2215 { "larr", 8592 },
2216 { "uarr", 8593 },
2217 { "rarr", 8594 },
2218 { "darr", 8595 },
2219 { "harr", 8596 },
2220 { "crarr", 8629 },
2221 { "lArr", 8656 },
2222 { "uArr", 8657 },
2223 { "rArr", 8658 },
2224 { "dArr", 8659 },
2225 { "hArr", 8660 },
2226 { "bull", 8226 },
2227 { "hellip", 8230 },
2228 { "prime", 8242 },
2229 { "oline", 8254 },
2230 { "frasl", 8260 },
2231 { "weierp", 8472 },
2232 { "image", 8465 },
2233 { "real", 8476 },
2234 { "trade", 8482 },
2235 { "euro", 8364 },
2236 { "alefsym", 8501 },
2237 { "spades", 9824 },
2238 { "clubs", 9827 },
2239 { "hearts", 9829 },
2240 { "diams", 9830 },
2241 { "ensp", 8194 },
2242 { "emsp", 8195 },
2243 { "thinsp", 8201 },
2244 { "zwnj", 8204 },
2245 { "zwj", 8205 },
2246 { "lrm", 8206 },
2247 { "rlm", 8207 },
2248 { "ndash", 8211 },
2249 { "mdash", 8212 },
2250 { "lsquo", 8216 },
2251 { "rsquo", 8217 },
2252 { "sbquo", 8218 },
2253 { "ldquo", 8220 },
2254 { "rdquo", 8221 },
2255 { "bdquo", 8222 },
2256 { "dagger", 8224 },
2257 { "Dagger", 8225 },
2258 { "permil", 8240 },
2259 { "lsaquo", 8249 },
2260 { "rsaquo", 8250 },
2261 { NULL, 0 }
2263 gint i;
2265 g_return_val_if_fail( NULL != htmlname, 0 );
2267 if( '\0' == *htmlname )
2268 return 0;
2270 if( '#' == *htmlname ) {
2271 const gchar *iter = htmlname;
2272 gunichar c = 0;
2273 if( 0 > len )
2274 i = 7;
2275 else
2276 i = len - 1;
2277 iter++;
2278 while( isdigit( *iter ) && ( 0 <= i ) ) {
2279 c = c * 10 + (*iter - '0');
2280 iter++;
2281 i--;
2284 if( 0 >= len ) {
2285 if( '\0' == *iter )
2286 return c;
2288 else if( 0 == i )
2289 return c;
2291 return 0;
2295 if( 0 > len ) {
2296 for( i = 0; NULL != html2utf8_table[ i ].str; i++ )
2297 if( 0 == strcmp( htmlname, html2utf8_table[ i ].str ) )
2298 return html2utf8_table[ i ].utf8;
2300 else if( 0 < len ) {
2301 for( i = 0; NULL != html2utf8_table[ i ].str; i++ )
2302 if( 0 == strncmp( htmlname, html2utf8_table[ i ].str, len ) )
2303 return html2utf8_table[ i ].utf8;
2306 return 0xFFFC;
2310 * Convert a string containing HTML encoded unichars to valid UTF-8.
2312 static gchar *htmlstr2utf8( const gchar* str )
2314 const gchar *amp_pos, *colon_pos, *copy_pos;
2315 gunichar uni = 0;
2316 GString *result;
2317 gsize len;
2319 if( NULL == str )
2320 return NULL;
2322 result = g_string_new( NULL );
2323 colon_pos = str;
2324 copy_pos = colon_pos;
2326 do {
2327 amp_pos = strchr( colon_pos, '&' );
2328 if( NULL == amp_pos )
2329 break;
2330 colon_pos = amp_pos;
2332 len = 0;
2333 while( (';' != *colon_pos)
2334 && ('\0' != *colon_pos) )
2336 colon_pos++;
2337 len++;
2340 if( (9 > len) && (2 < len) ) {
2341 uni = htmlname2unichar( amp_pos + 1, colon_pos - amp_pos - 1 );
2342 if( (0 != uni) && (TRUE == g_unichar_validate( uni )) ) {
2343 g_string_append_len( result, copy_pos, amp_pos - copy_pos );
2344 colon_pos++;
2345 copy_pos = colon_pos;
2346 g_string_append_unichar( result, uni );
2349 amp_pos = colon_pos;
2351 while( NULL != amp_pos );
2353 if( NULL != copy_pos )
2354 g_string_append( result, copy_pos );
2356 return g_string_free( result, FALSE );
2359 static gchar * strip_tags(gchar *html)
2361 gsize i = 0,j=0;
2362 unsigned depth = 0;
2363 while(html[i] != '\0') {
2364 if(html[i] == '<') depth++;
2365 else if(html[i] == '>') depth--;
2366 else if(depth == 0) {
2367 html[j] = html[i];
2368 j++;
2370 i++;
2372 html[j] = '\0';
2373 return html;
2376 gchar * meta_data_get_text_from_html(const MetaData *data)
2378 gchar *retv;
2379 g_assert(meta_data_is_html(data));
2380 retv = htmlstr2utf8((gchar *)data->content);
2381 /* need to strip tags */
2382 retv = strip_tags(retv);
2383 return retv;
2385 gboolean meta_data_is_raw(const MetaData *data)
2387 return data->content_type == META_DATA_CONTENT_RAW;
2390 const guchar * meta_data_get_raw(const MetaData *data, gsize *length)
2392 g_assert(meta_data_is_raw(data));
2393 if(length)
2394 *length = data->size;
2395 return (guchar *)data->content;
2397 gboolean meta_data_is_text_vector(const MetaData *data)
2399 return data->content_type == META_DATA_CONTENT_TEXT_VECTOR;
2401 const gchar ** meta_data_get_text_vector(const MetaData *data)
2403 g_assert(meta_data_is_text_vector(data));
2404 return (const gchar **)data->content;
2407 gboolean meta_data_is_text_list(const MetaData *data)
2409 return data->content_type == META_DATA_CONTENT_TEXT_LIST;
2411 const GList *meta_data_get_text_list(const MetaData *data)
2413 g_assert(meta_data_is_text_list(data));
2414 return (const GList *)data->content;
2417 * Plugin structure
2420 gmpcPrefPlugin metadata_pref_plug = {
2421 .construct = metadata_construct_pref_pane,
2422 .destroy = metadata_destroy_pref_pane
2424 gmpcPlugin metadata_plug = {
2425 .name = N_("Metadata Handler"),
2426 .version = {1,1,1},
2427 .plugin_type = GMPC_INTERNALL,
2428 // .pref = &metadata_pref_plug
2431 /* vim: set noexpandtab ts=4 sw=4 sts=4 tw=120: */