3 #include <glib/gstdio.h>
4 #include "qlib/qasyncqueue.h"
9 long unsigned num_queries
= 0;
11 config_obj
*cover_index
= NULL
;
12 int meta_num_plugins
=0;
13 gmpcPlugin
**meta_plugins
= NULL
;
15 GThread
*meta_thread
= NULL
;
17 * This is queue is used to send commands to the retrieval queue
19 QAsyncQueue
*meta_commands
= NULL
;
21 * This queue is used to send replies back.
23 QAsyncQueue
*meta_results
= NULL
;
25 GMutex
*meta_processing
= NULL
;
34 MetaDataResult result
;
37 MetaDataCallback callback
;
41 gboolean
meta_compare_func(meta_thread_data
*mt1
, meta_thread_data
*mt2
);
42 static gboolean
meta_data_handle_results(void);
45 static gboolean meta_data_handler_data_match(meta_thread_data *data, gpointer data2);
47 static mpd_Song
*rewrite_mpd_song(mpd_Song
*tsong
, MetaDataType type
)
49 mpd_Song
*edited
= NULL
;
50 /* If it is not a mpd got song */
51 if(tsong
->file
== NULL
)
53 if(type
&(META_ALBUM_ART
|META_ALBUM_TXT
))
55 MpdData
*data2
= NULL
;
56 mpd_database_search_start(connection
, TRUE
);
57 mpd_database_search_add_constraint(connection
, MPD_TAG_ITEM_ARTIST
, tsong
->artist
);
58 mpd_database_search_add_constraint(connection
, MPD_TAG_ITEM_ALBUM
, tsong
->album
);
59 data2
= mpd_database_search_commit(connection
);
67 if(type
&(META_ARTIST_ART
|META_ARTIST_TXT
))
69 MpdData
*data2
= NULL
;
70 mpd_database_search_start(connection
, TRUE
);
71 mpd_database_search_add_constraint(connection
, MPD_TAG_ITEM_ARTIST
, tsong
->artist
);
72 data2
= mpd_database_search_commit(connection
);
82 edited
= mpd_songDup(tsong
);
85 * Collections detection
86 * Only do this for album related queries.
88 if(type
&(META_ALBUM_ART
|META_ALBUM_TXT
))
90 if(edited
->album
&& edited
->file
)
94 char *dir
= g_path_get_dirname(edited
->file
);
95 mpd_database_search_start(connection
,TRUE
);
96 mpd_database_search_add_constraint(connection
, MPD_TAG_ITEM_ALBUM
, edited
->album
);
99 data2
= mpd_database_search_commit(connection
);
102 for(i
=0;data2
; data2
= mpd_data_get_next(data2
))
104 if(strncasecmp(data2
->song
->file
, dir
, strlen(dir
))==0)
106 /* Check for NULL pointers */
107 if(data2
->song
->artist
&& edited
->artist
)
109 if(strcmp(data2
->song
->artist
, edited
->artist
))
118 g_free(edited
->artist
);
119 edited
->artist
= g_strdup("Various Artists");
125 * Artist renaming, Clapton, Eric -> Eric Clapton
127 if(edited
->artist
&& cfg_get_single_value_as_int_with_default(config
, "metadata", "rename", FALSE
))
129 gchar
**str
= g_strsplit(edited
->artist
, ",", 2);
132 g_free(edited
->artist
);
133 edited
->artist
= g_strdup_printf("%s %s", g_strstrip(str
[1]), g_strstrip(str
[0]));
136 debug_printf(DEBUG_INFO
, "string converted to: '%s'", edited
->artist
);
141 static void meta_data_set_cache_real(mpd_Song
*song
, MetaDataType type
, MetaDataResult result
, char *path
)
145 * Save the path for the album art
147 if(type
== META_ALBUM_ART
) {
148 if(song
->artist
&& song
->album
) {
149 char *temp
= g_strdup_printf("album:%s", song
->album
);
150 if(result
== META_DATA_AVAILABLE
) {
151 cfg_set_single_value_as_string(cover_index
, song
->artist
, temp
,path
);
153 cfg_set_single_value_as_string(cover_index
, song
->artist
, temp
,"");
157 } else if(type
== META_ALBUM_TXT
) {
158 if(song
->artist
&& song
->album
) {
159 char *temp
= g_strdup_printf("albumtxt:%s", song
->album
);
160 if(result
== META_DATA_AVAILABLE
) {
161 cfg_set_single_value_as_string(cover_index
, song
->artist
, temp
,path
);
163 cfg_set_single_value_as_string(cover_index
, song
->artist
, temp
,"");
167 } else if (type
== META_ARTIST_ART
) {
169 char *temp
= g_strdup("image");
170 if(result
== META_DATA_AVAILABLE
) {
171 cfg_set_single_value_as_string(cover_index
, song
->artist
, temp
,path
);
173 cfg_set_single_value_as_string(cover_index
, song
->artist
, temp
,"");
177 } else if (type
== META_ARTIST_TXT
) {
179 char *temp
= g_strdup("biography");
180 if(result
== META_DATA_AVAILABLE
) {
181 cfg_set_single_value_as_string(cover_index
, song
->artist
, temp
,path
);
183 cfg_set_single_value_as_string(cover_index
, song
->artist
, temp
,"");
187 } else if (type
== META_ARTIST_SIMILAR
) {
189 char *temp
= g_strdup("similar");
190 if(result
== META_DATA_AVAILABLE
) {
191 cfg_set_single_value_as_string(cover_index
, song
->artist
, temp
,path
);
193 cfg_set_single_value_as_string(cover_index
, song
->artist
, temp
,"");
197 } else if (type
== META_SONG_TXT
) {
198 if(song
->artist
&& song
->title
) {
199 char *temp
= g_strdup_printf("lyrics:%s", song
->title
);
200 if(result
== META_DATA_AVAILABLE
) {
201 cfg_set_single_value_as_string(cover_index
, song
->artist
, temp
,path
);
203 cfg_set_single_value_as_string(cover_index
, song
->artist
, temp
,"");
209 void meta_data_set_cache(mpd_Song
*song
, MetaDataType type
, MetaDataResult result
, char *path
)
211 mpd_Song
*edited
= rewrite_mpd_song(song
, type
);
212 meta_data_set_cache_real(edited
, type
, result
, path
);
215 if(strcmp(edited
->artist
, "Various Artists")!=0)
216 meta_data_set_cache_real(song
, type
, result
, path
);
218 mpd_freeSong(edited
);
223 * !!NEEDS TO BE THREAD SAFE !!
227 MetaDataResult
meta_data_get_from_cache(mpd_Song
*song
, MetaDataType type
, char **path
)
231 return META_DATA_UNAVAILABLE
;
233 /* Get values acording to type */
234 if(type
== META_ALBUM_ART
)
237 if(!song
->artist
|| !song
->album
)
239 return META_DATA_UNAVAILABLE
;
241 temp
= g_strdup_printf("album:%s", song
->album
);
242 *path
= cfg_get_single_value_as_string(cover_index
,song
->artist
, temp
);
246 /* if path length is NULL, then data unavailible */
247 if(strlen(*path
) == 0)
252 return META_DATA_UNAVAILABLE
;
254 /* return that data is availible */
255 if(!g_file_test(*path
, G_FILE_TEST_EXISTS
))
257 temp
= g_strdup_printf("album:%s", song
->album
);
258 cfg_del_single_value(cover_index
, song
->artist
, temp
);
262 return META_DATA_FETCHING
;
264 return META_DATA_AVAILABLE
;
267 /* else default to fetching */
269 /* Get values acording to type */
270 else if(type
== META_ARTIST_SIMILAR
)
274 return META_DATA_UNAVAILABLE
;
276 *path
= cfg_get_single_value_as_string(cover_index
,song
->artist
, "similar");
279 /* if path length is NULL, then data unavailible */
280 if(strlen(*path
) == 0)
285 return META_DATA_UNAVAILABLE
;
287 return META_DATA_AVAILABLE
;
290 /* else default to fetching */
293 else if(type
== META_ALBUM_TXT
)
296 if(!song
->artist
|| !song
->album
)
298 return META_DATA_UNAVAILABLE
;
300 temp
= g_strdup_printf("albumtxt:%s", song
->album
);
301 *path
= cfg_get_single_value_as_string(cover_index
,song
->artist
, temp
);
305 /* if path length is NULL, then data unavailible */
306 if(strlen(*path
) == 0)
310 return META_DATA_UNAVAILABLE
;
312 /* return that data is availible */
313 if(!g_file_test(*path
, G_FILE_TEST_EXISTS
))
315 temp
= g_strdup_printf("albumtxt:%s", song
->album
);
316 cfg_del_single_value(cover_index
, song
->artist
, temp
);
320 return META_DATA_FETCHING
;
322 /* return that data is availible */
323 return META_DATA_AVAILABLE
;
326 else if (type
== META_ARTIST_ART
)
331 return META_DATA_UNAVAILABLE
;
333 temp
= g_strdup("image");
334 *path
= cfg_get_single_value_as_string(cover_index
,song
->artist
, temp
);
338 /* if path length is NULL, then data unavailible */
339 if(strlen(*path
) == 0)
343 return META_DATA_UNAVAILABLE
;
345 /* return that data is availible */
346 if(!g_file_test(*path
, G_FILE_TEST_EXISTS
))
348 temp
= g_strdup("image");
349 cfg_del_single_value(cover_index
, song
->artist
, temp
);
353 return META_DATA_FETCHING
;
355 /* return that data is availible */
356 return META_DATA_AVAILABLE
;
359 else if (type
== META_ARTIST_TXT
)
364 return META_DATA_UNAVAILABLE
;
366 temp
= g_strdup("biography");
367 *path
= cfg_get_single_value_as_string(cover_index
,song
->artist
, temp
);
371 /* if path length is NULL, then data unavailible */
372 if(strlen(*path
) == 0)
376 return META_DATA_UNAVAILABLE
;
378 /* return that data is availible */
379 if(!g_file_test(*path
, G_FILE_TEST_EXISTS
))
381 temp
= g_strdup("biography");
382 cfg_del_single_value(cover_index
, song
->artist
, temp
);
386 return META_DATA_FETCHING
;
388 /* return that data is availible */
389 return META_DATA_AVAILABLE
;
393 if(type
== META_SONG_TXT
)
396 if(!song
->artist
|| !song
->title
)
398 return META_DATA_UNAVAILABLE
;
400 temp
= g_strdup_printf("lyrics:%s", song
->title
);
401 *path
= cfg_get_single_value_as_string(cover_index
,song
->artist
, temp
);
405 /* if path length is NULL, then data unavailible */
406 if(strlen(*path
) == 0)
410 return META_DATA_UNAVAILABLE
;
412 /* return that data is availible */
413 if(!g_file_test(*path
, G_FILE_TEST_EXISTS
))
415 temp
= g_strdup_printf("lyrics:%s",song
->title
);
416 cfg_del_single_value(cover_index
, song
->artist
, temp
);
420 return META_DATA_FETCHING
;
422 /* return that data is availible */
423 return META_DATA_AVAILABLE
;
426 return META_DATA_FETCHING
;
429 static void meta_data_retrieve_thread()
431 meta_thread_data
*data
= NULL
;
433 * A continues loop, waiting for new commands to arrive
437 * Get command from queue
440 data
= q_async_queue_pop(meta_commands
);
441 /* check if quit signal */
444 debug_printf(DEBUG_INFO
, "Quit command recieved.. quitting");
447 g_mutex_lock(meta_processing
);
449 * Set default return values
450 * Just to be sure, init them.
453 data
->result
= META_DATA_UNAVAILABLE
;
454 data
->result_path
= NULL
;
457 * Check cache *again*
458 * because between the time this command was commited, and the time
459 * we start processing it, the result may allready been retrieved
461 if(data
->type
&META_QUERY_NO_CACHE
)
463 data
->result
= META_DATA_FETCHING
;
467 data
->result
= meta_data_get_from_cache(data
->edited
/*song*/,data
->type
&META_QUERY_DATA_TYPES
, &(data
->result_path
));
470 * Handle cache result.
471 * If the cache returns it doesn't have anything (that it needs fetching)
474 if(data
->result
== META_DATA_FETCHING
)
479 * Set default return values
480 * Need to be reset, because of cache fetch
482 data
->result
= META_DATA_UNAVAILABLE
;
483 data
->result_path
= NULL
;
485 * start fetching the results
488 * Loop through all the plugins until we don't have plugins anymore, or we have a result.
490 for(i
=0;i
<(meta_num_plugins
) && data
->result
!= META_DATA_AVAILABLE
;i
++)
493 * Get image function is only allowed to return META_DATA_AVAILABLE or META_DATA_UNAVAILABLE
495 if(meta_plugins
[i
]->get_enabled())
497 data
->result
= meta_plugins
[i
]->metadata
->get_image(data
->edited
, data
->type
&META_QUERY_DATA_TYPES
, &path
);
498 data
->result_path
= path
;
505 meta_data_set_cache_real(data
->edited
, data
->type
&META_QUERY_DATA_TYPES
, data
->result
, data
->result_path
);
506 if(data
->edited
->artist
)
508 if(strcmp(data
->edited
->artist
, "Various Artists")!=0)
509 meta_data_set_cache_real(data
->song
, data
->type
&META_QUERY_DATA_TYPES
, data
->result
, data
->result_path
);
513 * Push the result back
515 q_async_queue_push(meta_results
, data
);
519 * clear our reference to the object
522 g_mutex_unlock(meta_processing
);
524 g_idle_add((GSourceFunc
)meta_data_handle_results
,NULL
);
528 static gboolean
meta_data_handle_results(void)
530 meta_thread_data
*data
= NULL
;
532 if(meta_thread
== g_thread_self())
534 debug_printf(DEBUG_ERROR
,"Crap, handled in wrong thread\n");
538 * Check if there are results to handle
539 * do this until the list is clear
541 for(data
= q_async_queue_try_pop(meta_results
);data
;
542 data
= q_async_queue_try_pop(meta_results
)) {
544 gmpc_meta_watcher_data_changed(gmw
,data
->song
, (data
->type
)&META_QUERY_DATA_TYPES
, data
->result
,data
->result_path
);
547 data
->callback(data
->song
,data
->result
,data
->result_path
, data
->data
);
550 if(data
->result_path
)q_free(data
->result_path
);
551 mpd_freeSong(data
->song
);
552 mpd_freeSong(data
->edited
);
556 * Keep the timer running
558 /* update when handled */
559 test
= g_mutex_trylock(meta_processing
);
561 g_mutex_unlock(meta_processing
);
562 gmpc_meta_watcher_queue_size_changed(gmw
, q_async_queue_true_length(meta_commands
)+!test
, num_queries
);
569 void meta_data_init()
571 gchar
*url
= gmpc_get_covers_path(NULL
);
572 if(!g_file_test(url
,G_FILE_TEST_IS_DIR
)){
573 if(g_mkdir(url
, 0700)<0){
574 g_error("Cannot make %s\n", url
);
578 url
= gmpc_get_covers_path("covers.db");
579 cover_index
= cfg_open(url
);
585 meta_commands
= q_async_queue_new();
589 meta_results
= q_async_queue_new();
591 meta_processing
= g_mutex_new();
593 * Create the retrieval thread
595 meta_thread
= g_thread_create((GThreadFunc
)meta_data_retrieve_thread
, NULL
, TRUE
, NULL
);
599 void meta_data_add_plugin(gmpcPlugin
*plug
)
604 meta_plugins
= g_realloc(meta_plugins
,(meta_num_plugins
+1)*sizeof(gmpcPlugin
**));
605 meta_plugins
[meta_num_plugins
-1] = plug
;
606 meta_plugins
[meta_num_plugins
] = NULL
;
610 for(i
=0; i
< (meta_num_plugins
-1);i
++)
612 if(meta_plugins
[i
]->metadata
->get_priority() > meta_plugins
[i
+1]->metadata
->get_priority())
614 gmpcPlugin
*temp
= meta_plugins
[i
];
616 meta_plugins
[i
] = meta_plugins
[i
+1];
617 meta_plugins
[i
+1] = temp
;
623 void meta_data_cleanup(void)
625 cfg_do_special_cleanup(cover_index
);
627 static gboolean
meta_data_check_plugin_changed_message(gpointer data
)
629 playlist3_show_error_message(_("A new metadata plugin was added, gmpc has purged all failed hits from the cache"), ERROR_INFO
);
632 void meta_data_check_plugin_changed()
634 int old_amount
= cfg_get_single_value_as_int_with_default(config
, "metadata", "num_plugins", 0);
635 if(old_amount
< meta_num_plugins
)
638 GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_OK,
639 _("A new metadata plugin was added, gmpc will now purge all missing metadata from the cache"));
640 g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);
641 gtk_widget_show_all(GTK_WIDGET(dialog));
643 gtk_init_add(meta_data_check_plugin_changed_message
, NULL
);
646 if(old_amount
!= meta_num_plugins
)
648 cfg_set_single_value_as_int(config
, "metadata", "num_plugins", meta_num_plugins
);
652 void meta_data_destroy(void)
654 meta_thread_data
*mtd
= NULL
;
658 debug_printf(DEBUG_INFO
,"Waiting for meta thread to terminate...");
659 /* remove old stuff */
660 q_async_queue_lock(meta_commands
);
661 while((mtd
= q_async_queue_try_pop_unlocked(meta_commands
)))
663 mpd_freeSong(mtd
->song
);
664 mpd_freeSong(mtd
->edited
);
667 /* Create the quit signal, this is just an empty request with id 0 */
668 mtd
= g_malloc0(sizeof(*mtd
));
671 /* push the request to the thread */
672 q_async_queue_push_unlocked(meta_commands
, mtd
);
673 q_async_queue_unlock(meta_commands
);
674 /* wait for the thread to finish */
675 g_thread_join(meta_thread
);
678 debug_printf(DEBUG_INFO
,"Done..");
681 g_mutex_free(meta_processing
);
683 q_async_queue_unref(meta_commands
);
684 meta_processing
= NULL
;
685 /* Close the cover database */
686 cfg_close(cover_index
);
688 gboolean
meta_compare_func(meta_thread_data
*mt1
, meta_thread_data
*mt2
)
690 if((mt1
->type
&META_QUERY_DATA_TYPES
) != (mt2
->type
&META_QUERY_DATA_TYPES
))
692 if(!gmpc_meta_watcher_match_data(mt1
->type
&META_QUERY_DATA_TYPES
, mt1
->song
, mt2
->song
))
699 * Function called by the "client"
701 MetaDataResult
meta_data_get_path(mpd_Song
*tsong
, MetaDataType type
, gchar
**path
,MetaDataCallback callback
, gpointer data
)
704 meta_thread_data
*mtd
= NULL
;
705 mpd_Song
*song
=NULL
;
708 /* TODO: Validate request */
711 * If there is no song
716 return META_DATA_UNAVAILABLE
;
720 * Check cache for result.
722 if(type
&META_QUERY_NO_CACHE
)
725 gmpc_meta_watcher_data_changed(gmw
,tsong
, (type
)&META_QUERY_DATA_TYPES
,META_DATA_FETCHING
, NULL
);
728 callback(song
,META_DATA_FETCHING
,NULL
,data
);
730 ret
= META_DATA_FETCHING
;
734 ret
= meta_data_get_from_cache(tsong
, type
&META_QUERY_DATA_TYPES
, path
);
736 * If the data is know. (and doesn't need fectching)
737 * call the callback and stop
739 if(ret
!= META_DATA_FETCHING
)
745 mtd
= g_malloc0(sizeof(*mtd
));
749 mtd
->edited
= rewrite_mpd_song(tsong
, type
);
752 * Query cache, but for changed artist name
754 if((type
&META_QUERY_NO_CACHE
) == 0)
756 ret
= meta_data_get_from_cache(mtd
->edited
, type
&META_QUERY_DATA_TYPES
, path
);
758 * If the data is know. (and doesn't need fectching)
759 * call the callback and stop
761 if(ret
!= META_DATA_FETCHING
)
763 /* store it under the original */
764 meta_data_set_cache_real(tsong
, type
, ret
, *path
);
765 mpd_freeSong(mtd
->edited
);
773 * If no result, start a thread and start fetching the data from there
779 * Not needed, but can be usefull for debugging
781 mtd
->song
= mpd_songDup(tsong
);
783 id
= mtd
->id
= g_random_int_range(1,2147483647);
785 mtd
->callback
= callback
;
788 * Check if request is allready in queue
790 /* when using old api style, the request is commited anyway */
793 /* if it is allready in the queue, there is no need to push it again
794 * because GmpcMetaWatcher signal will arrive at every widget
797 * TODO: this misses items currently in the queue, try to catch that too.
799 q_async_queue_lock(meta_commands
);
800 if(q_async_queue_has_data(meta_commands
,(GCompareFunc
)meta_compare_func
, mtd
))
802 q_async_queue_unlock(meta_commands
);
803 mpd_freeSong(mtd
->song
);
804 mpd_freeSong(mtd
->edited
);
808 q_async_queue_unlock(meta_commands
);
811 /** push it to the other thread */
814 /* I should fix this */
815 test
= g_mutex_trylock(meta_processing
);
817 g_mutex_unlock(meta_processing
);
818 gmpc_meta_watcher_queue_size_changed(gmw
, q_async_queue_true_length(meta_commands
)+!test
, num_queries
);
820 q_async_queue_push(meta_commands
, mtd
);
821 /** clean reference to pointer, it's now to the other thread */