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.
22 #include <glib/gstdio.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"
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
;
68 static GAsyncQueue
*gaq
= NULL
;
69 static GAsyncQueue
*return_queue
= NULL
;
70 const char *plug_name
= "glyr";
71 static GlyrDatabase
*db
= NULL
;
75 * This queue is used to send replies back.
77 //GQueue *meta_results = NULL;
81 * Structure holding a metadata query */
84 /* unique id for the query (unused)*/
86 /* The callback to call when the query is done, or NULL */
87 MetaDataCallback callback
;
88 /* Callback user_data pointer*/
90 /* The song the data is queries for */
92 /* The song modified by the search system.
93 * This does albumartist, tag cleanup etc */
95 /* The type of metadata */
98 MetaDataResult result
;
99 /* The actual result data */
101 /* The index of the plugin being queried */
106 /* List with temporary result from plugin index */
108 /* The current position in the list */
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
);
135 edited
= data2
->song
;
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
);
147 edited
= data2
->song
;
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
);
163 edited
= data2
->song
;
165 mpd_data_free(data2
);
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
)
181 g_free(edited
->artist
);
182 edited
->artist
= g_strdup(edited
->albumartist
);
185 else if(edited
->album
&& edited
->file
)
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
);
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
))
213 g_free(edited
->artist
);
214 edited
->artist
= g_strdup("Various Artists");
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]));
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
))
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
];
256 /* Remove trailing and leading spaces */
257 edited
->album
= g_strstrip(edited
->album
);
262 char *title
= edited
->title
;
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
];
276 /* Remove trailing and leading spaces */
277 edited
->title
= g_strstrip(edited
->title
);
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
);
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
);
299 mtd
->callback(mtd
->song
, mtd
->result
, mtd
->met
, mtd
->data
);
302 /* Free any possible plugin results */
304 g_list_foreach(mtd->list, (GFunc)meta_data_free, NULL);
305 g_list_free(mtd->list);
307 */ /* Free the result data */
309 meta_data_free(mtd
->met
);
310 /* Free the copie and edited version of the songs */
312 mpd_freeSong(mtd
->song
);
314 mpd_freeSong(mtd
->edited
);
316 /* Free the Request struct */
323 static MetaDataContentType
setup_glyr_query(GlyrQuery
*query
,
324 const meta_thread_data
*mtd
)
326 MetaDataContentType content_type
= META_DATA_CONTENT_RAW
;
329 glyr_opt_force_utf8(query
, TRUE
);
331 glyr_opt_parallel(query
, 4);
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
&&
392 glyr_opt_type(query
, GLYR_GET_GUITARTABS
);
393 content_type
= META_DATA_CONTENT_TEXT
;
396 g_warning("Unsupported metadata type, or insufficient info");
400 static MetaData
* glyr_get_similiar_song_names(GlyrMemCache
* cache
)
402 MetaData
* mtd
= NULL
;
405 if(cache
->data
!= NULL
)
407 gchar
** split
= g_strsplit(cache
->data
,"\n",0);
408 if(split
!= NULL
&& split
[0] != NULL
)
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
;
419 buffer
= g_strdup_printf("%s::%s",split
[1],split
[0]);
420 g_log(LOG_DOMAIN
,G_LOG_LEVEL_DEBUG
, "%s\n", buffer
);
423 mtd
->content
= g_list_append((GList
*) mtd
->content
, buffer
);
432 static MetaData
* glyr_get_similiar_artist_names(GlyrMemCache
* cache
)
434 MetaData
* mtd
= NULL
;
437 if(cache
->data
!= NULL
)
439 gchar
** split
= g_strsplit(cache
->data
,"\n",0);
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
;
450 mtd
->content
= g_list_append((GList
*) mtd
->content
,
451 g_strdup((char *)split
[0]));
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
;
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
);
478 mtd
->result
= META_DATA_AVAILABLE
;
482 else if (mtd
->type
== META_SONG_SIMILAR
)
485 cont
= glyr_get_similiar_song_names(cache
);
489 mtd
->result
= META_DATA_AVAILABLE
;
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
;
508 // Explicitely not found.
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
)
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.
535 printf("Quitting....");
536 g_mutex_unlock(exit_handle_lock
);
537 /* Free the Request struct */
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
);
567 content_type
= setup_glyr_query(&query
, mtd
);
570 cache
= glyr_get(&query
,&err
,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");
580 glyr_db_insert(db
,&query
, cache
);
581 printf("Cache is Empty\n");
583 mtd
->result
= META_DATA_UNAVAILABLE
;
585 process_glyr_result(cache
,content_type
, mtd
);
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.
599 // Schedule the result thread in idle time.
600 g_idle_add(glyr_return_queue
, NULL
);
607 GThread
*gaq_fetcher_thread
= NULL
;
608 void meta_data_init(void)
612 /* Is this function thread safe? */
613 url
= gmpc_get_covers_path("");
615 exit_handle_lock
= g_mutex_new();
618 db
= glyr_db_init(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
);
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)
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
];
651 meta_plugins
[i
] = meta_plugins
[i
+1];
652 meta_plugins
[i
+1] = temp
;
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
);
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
;
686 /* Iterate through the list and destroy all entries */
687 for(iter
= g_list_first(process_queue
); iter
; iter
= iter
->next
)
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 */
697 meta_data_free(mtd
->met
);
698 /* destroy the copied and modified song */
700 mpd_freeSong(mtd
->song
);
702 mpd_freeSong(mtd
->edited
);
704 /* Free the Request struct */
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 */
714 metadata_cache_destroy();
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 */
725 g_list_foreach(mtd->list, (GFunc)meta_data_free, NULL);
726 g_list_free(mtd->list);
729 */ /* Free the result data */
731 meta_data_free(mtd
->met
);
732 /* Free the copie and edited version of the songs */
734 mpd_freeSong(mtd
->song
);
736 mpd_freeSong(mtd
->edited
);
738 /* Free the Request struct */
741 mtd
= g_malloc0(sizeof(*mtd
));
743 g_async_queue_push_unlocked(gaq
, mtd
);
745 g_async_queue_unlock(gaq
);
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
);
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 */
767 g_list_foreach(mtd->list, (GFunc)meta_data_free, NULL);
768 g_list_free(mtd->list);
770 */ /* Free the result data */
772 meta_data_free(mtd
->met
);
773 /* Free the copie and edited version of the songs */
775 mpd_freeSong(mtd
->song
);
777 mpd_freeSong(mtd
->edited
);
779 /* Free the Request struct */
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
))
791 if(!gmpc_meta_watcher_match_data(mt1
->type
&META_QUERY_DATA_TYPES
, mt1
->song
, mt2
->song
))
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 */
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 */
832 g_list_foreach(data
->list
, (GFunc
)meta_data_free
, NULL
);
833 g_list_free(data
->list
);
835 /* Free the result data */
837 meta_data_free(data
->met
);
838 /* Free the copie and edited version of the songs */
840 mpd_freeSong(data
->song
);
842 mpd_freeSong(data
->edited
);
844 /* Free the Request struct */
847 /* Make the idle handler stop */
852 * This function handles it when data needs to be downloaded (in the cache) for now that are images.
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 */
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
);
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 */
876 g_file_set_contents(filename
, data
, length
, &error
);
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
;
890 /* Convert ownership of string to d->met */
892 /* Free any remaining results */
893 g_list_foreach(d
->list
,(GFunc
) meta_data_free
, NULL
);
894 g_list_free(d
->list
);
898 /* by setting index to the last, the process_itterate knows it should not look further */
899 d
->index
= meta_num_plugins
;
902 g_idle_add((GSourceFunc
)process_itterate
, NULL
);
903 //process_itterate();
904 /* we trown it back, quit */
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
;
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 */
920 * It seems we somehow _failed_ to get data, lets try the next result
924 /* If there is a result entry, advanced it to the next entry.*/
925 d
->iter
= g_list_next(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 */
937 /* Remove it from the result list, so it does not get deleted twice */
939 /* set result available */
940 d
->result
= META_DATA_AVAILABLE
;
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 */
955 /* if it is not something we need to download, se the result as the final result */
956 d
->result
= META_DATA_AVAILABLE
;
958 /* remove result from list */
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
968 g_list_foreach(d
->list
,(GFunc
) meta_data_free
, NULL
);
969 g_list_free(d
->list
);
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
;
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
)
988 meta_thread_data
*d
= (meta_thread_data
*)user_data
;
991 * This plugin didn't have a result
992 * Skip to next plugin,
999 g_idle_add((GSourceFunc
)process_itterate
, NULL
);
1000 //process_itterate();
1005 * Plugin has a result, try the downloading the first, if that fails
1008 /* Set the first one as the current one */
1009 d
->iter
= g_list_first(list
);
1011 /* Get the result of the first query */
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
;
1021 d
->iter
->data
= NULL
;
1023 /* if it is a remote uri, download it */
1025 GEADAsyncHandler
*handle
= gmpc_easy_async_downloader(path
, metadata_download_handler
, d
);
1028 /* if download is a success wait for result */
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
);
1039 md
->content_type
= META_DATA_CONTENT_URI
;
1040 md
->content
= filename
;
1041 d
->result
= META_DATA_AVAILABLE
;
1043 d
->iter
->data
= NULL
;
1045 /* if it is not something to download, set the result as final */
1046 d
->result
= META_DATA_AVAILABLE
;
1048 d
->iter
->data
= NULL
;
1051 /* Free the remaining results. */
1054 g_list_foreach(d
->list
, (GFunc
)meta_data_free
, NULL
);
1055 g_list_free(d
->list
);
1061 * If still no result, try next plugin
1063 if(d
->result
!= META_DATA_AVAILABLE
)
1068 * If we have a result.
1069 * Make it not check other plugins
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)
1085 meta_thread_data
*d
= NULL
;
1086 /* If queue is empty, do nothing */
1087 if(process_queue
== NULL
){
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
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
;
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 */
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
];
1133 if(gmpc_plugin_get_enabled(plug
))
1135 gmpc_plugin_metadata_query_metadata_list(plug
,
1137 d
->type
&META_QUERY_DATA_TYPES
,
1144 /* advance to the next plugin */
1146 /* do the next itteration */
1147 //process_itterate();
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
;
1159 d
->do_rename
= FALSE
;
1161 d
->rename_done
= TRUE
;
1164 /* revert changes */
1167 mpd_Song
*song
= d
->edited
;
1168 d
->edited
= d
->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 */
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
);
1197 length1
= g_list_length(process_queue
);
1199 process_queue
= g_list_delete_link(process_queue
, 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
);
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
);
1216 /* if there is no old-type callback, We can remove it completely */
1219 meta_data_free(d2
->met
);
1220 /* free stored song copies */
1222 mpd_freeSong(d2
->edited
);
1224 mpd_freeSong(d2
->song
);
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
);
1247 /* Make it be called again, if there are still items in the queue*/
1250 // g_idle_add((GSourceFunc)process_itterate, __FUNCTION__);
1251 //process_itterate();
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
;
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
);
1275 mtd
->callback
= callback
;
1276 /* the callback data */
1278 /* start at the first plugin */
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 */
1287 * If requested query the cache first
1289 if((type
&META_QUERY_NO_CACHE
) == 0)
1291 MetaDataContentType content_type
= META_DATA_CONTENT_RAW
;
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
))
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
);
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
);
1323 return META_DATA_FETCHING
;
1325 // static int test_id = 0;
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;
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
);
1352 callback(song
,META_DATA_FETCHING
,NULL
,data
);
1354 ret
= META_DATA_FETCHING
;
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
)
1367 if(*met
) meta_data_free(*met
);
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
);
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
);
1398 /* If it is fetching free the result */
1399 if(*met
) meta_data_free(*met
);
1405 * If no result, start a thread and start fetching the data from there
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
);
1419 mtd
->callback
= callback
;
1420 /* the callback data */
1422 /* start at the first plugin */
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 */
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
;
1446 static gchar
* strip_invalid_chars(gchar
*input
)
1450 g_assert(input
!= NULL
);
1451 length
= strlen(input
);
1452 if(input
== NULL
) return NULL
;
1453 for(i
=0;i
<length
;i
++)
1475 * Helper function for storing metadata
1477 gchar
* gmpc_get_metadata_filename(MetaDataType type
, mpd_Song
*song
, char *ext
)
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);
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);
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);
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
);
1527 if(!g_file_test(retv
, G_FILE_TEST_IS_DIR
)) {
1528 g_error("File exists but is not a directory: %s\n", retv
);
1532 if(type
&(META_ALBUM_ART
|META_ALBUM_TXT
)) {
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
);
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
) {
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
);
1548 }else if (type
&META_SONG_GUITAR_TAB
) {
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
);
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
);
1562 static void metadata_pref_priority_changed(GtkCellRenderer
*renderer
, char *path
, char *new_text
, GtkListStore
*store
)
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);
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
,
1586 gmpcPluginParent
*plug
;
1587 gtk_tree_model_get(GTK_TREE_MODEL(model
), iter
, 0, &plug
, -1);
1590 gboolean active
= gmpc_plugin_get_enabled(plug
);
1591 g_object_set(G_OBJECT(cell
), "active", active
, NULL
);
1597 static void __column_toggled_enabled(GtkCellRendererToggle
*renderer
,
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);
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
;
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
);
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
);
1642 renderer
= gtk_cell_renderer_toggle_new();
1643 gtk_tree_view_insert_column_with_data_func(GTK_TREE_VIEW(treeview
),
1647 (GtkTreeCellDataFunc
)__column_data_func_enabled
,
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
),
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
),
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
++)
1684 gtk_list_store_insert_with_values(store
, &iter
, -1,
1686 1, gmpc_plugin_get_name(meta_plugins
[i
]),
1687 2, gmpc_plugin_metadata_get_priority(meta_plugins
[i
]),
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
));
1701 gtk_widget_destroy(child
);
1706 * Get a list of urls, used by f.e. selector
1709 typedef struct MLQuery
{
1712 void (*callback
)(gpointer handle
,const gchar
*plugin_name
, GList
*list
, gpointer data
);
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
);
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");
1734 g_list_foreach(list
,(GFunc
) meta_data_free
, NULL
);
1739 mpd_freeSong(q
->song
);
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
);
1751 if(q
->index
< meta_num_plugins
)
1753 gmpcPluginParent
*plug
= meta_plugins
[q
->index
];
1755 if(gmpc_plugin_get_enabled(plug
)){
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
);
1763 /* indicate done, by calling with NULL handle */
1764 q
->callback(q
,NULL
, NULL
, q
->userdata
);
1766 mpd_freeSong(q
->song
);
1770 void metadata_get_list_cancel(gpointer data
)
1773 MLQuery
*q
= (MLQuery
*)data
;
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
);
1782 MLQuery
*q
= g_malloc0(sizeof(*q
));
1785 q
->callback
= callback
;
1790 * Create a copy, so song is guarantee to be valid during queries of plugins
1792 q
->song
= mpd_songDup(song
);
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
);
1807 g_log("MetaData", G_LOG_LEVEL_DEBUG
, "Cleanup done");
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
);
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
;
1830 void meta_data_free(MetaData
*data
)
1832 if(data
== NULL
) return;
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
;
1848 if(data
->thumbnail_uri
) g_free(data
->thumbnail_uri
);
1849 data
->thumbnail_uri
= NULL
;
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
)
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 */
1892 retv
->content
= NULL
;
1894 retv
->content
= g_strdup((gchar
*)data
->content
);
1897 if(data
->thumbnail_uri
!= NULL
) {
1898 retv
->thumbnail_uri
= g_strdup(data
->thumbnail_uri
);
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
;
1916 data
->content
= NULL
;
1917 retv
->thumbnail_uri
= data
->thumbnail_uri
;
1918 data
->thumbnail_uri
= NULL
;
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
);
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
;
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
);
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 & and 
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
2017 html2utf8_table
[] = {
2167 { "thetasym", 977 },
2236 { "alefsym", 8501 },
2265 g_return_val_if_fail( NULL
!= htmlname
, 0 );
2267 if( '\0' == *htmlname
)
2270 if( '#' == *htmlname
) {
2271 const gchar
*iter
= htmlname
;
2278 while( isdigit( *iter
) && ( 0 <= i
) ) {
2279 c
= c
* 10 + (*iter
- '0');
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
;
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
;
2322 result
= g_string_new( NULL
);
2324 copy_pos
= colon_pos
;
2327 amp_pos
= strchr( colon_pos
, '&' );
2328 if( NULL
== amp_pos
)
2330 colon_pos
= amp_pos
;
2333 while( (';' != *colon_pos
)
2334 && ('\0' != *colon_pos
) )
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
);
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
)
2363 while(html
[i
] != '\0') {
2364 if(html
[i
] == '<') depth
++;
2365 else if(html
[i
] == '>') depth
--;
2366 else if(depth
== 0) {
2376 gchar
* meta_data_get_text_from_html(const MetaData
*data
)
2379 g_assert(meta_data_is_html(data
));
2380 retv
= htmlstr2utf8((gchar
*)data
->content
);
2381 /* need to strip tags */
2382 retv
= strip_tags(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
));
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
;
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"),
2427 .plugin_type
= GMPC_INTERNALL
,
2428 // .pref = &metadata_pref_plug
2431 /* vim: set noexpandtab ts=4 sw=4 sts=4 tw=120: */