Tighten top bar metadata browser a bit
[gmpc.git] / src / metadata.c
blob01f8fea82ff33eaa04c25e7b7e15eb3d49e12138
1 #include <string.h>
2 #include <glib.h>
3 #include <glib/gstdio.h>
4 #include "qlib/qasyncqueue.h"
5 #include "main.h"
7 #include "metadata.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;
16 /**
17 * This is queue is used to send commands to the retrieval queue
19 QAsyncQueue *meta_commands = NULL;
20 /**
21 * This queue is used to send replies back.
23 QAsyncQueue *meta_results = NULL;
25 GMutex *meta_processing = NULL;
27 typedef struct {
28 guint id;
29 /* Data */
30 mpd_Song *song;
31 mpd_Song *edited;
32 MetaDataType type;
33 /* Resuls */
34 MetaDataResult result;
35 char *result_path;
36 /* Callback */
37 MetaDataCallback callback;
38 gpointer data;
39 } meta_thread_data;
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);
60 if(data2)
62 edited = data2->song;
63 data2->song = NULL;
64 mpd_data_free(data2);
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);
73 if(data2)
75 edited = data2->song;
76 data2->song = NULL;
77 mpd_data_free(data2);
81 if(!edited)
82 edited = mpd_songDup(tsong);
84 /**
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)
92 int i=0;
93 MpdData *data2;
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);
100 if(data2)
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))
110 i++;
115 if(i >=3)
117 if(edited->artist)
118 g_free(edited->artist);
119 edited->artist = g_strdup("Various Artists");
121 g_free(dir);
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);
131 if(str[1]) {
132 g_free(edited->artist);
133 edited->artist = g_strdup_printf("%s %s", g_strstrip(str[1]), g_strstrip(str[0]));
135 g_strfreev(str);
136 debug_printf(DEBUG_INFO, "string converted to: '%s'", edited->artist);
138 return edited;
141 static void meta_data_set_cache_real(mpd_Song *song, MetaDataType type, MetaDataResult result, char *path)
143 if(!song) return;
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);
152 } else {
153 cfg_set_single_value_as_string(cover_index, song->artist, temp,"");
155 q_free(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);
162 } else {
163 cfg_set_single_value_as_string(cover_index, song->artist, temp,"");
165 q_free(temp);
167 } else if (type == META_ARTIST_ART) {
168 if(song->artist) {
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);
172 } else {
173 cfg_set_single_value_as_string(cover_index, song->artist, temp,"");
175 q_free(temp);
177 } else if (type == META_ARTIST_TXT) {
178 if(song->artist) {
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);
182 } else {
183 cfg_set_single_value_as_string(cover_index, song->artist, temp,"");
185 q_free(temp);
187 } else if (type == META_ARTIST_SIMILAR) {
188 if(song->artist) {
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);
192 } else {
193 cfg_set_single_value_as_string(cover_index, song->artist, temp,"");
195 q_free(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);
202 } else {
203 cfg_set_single_value_as_string(cover_index, song->artist, temp,"");
205 q_free(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);
213 if(edited->artist)
215 if(strcmp(edited->artist, "Various Artists")!=0)
216 meta_data_set_cache_real(song, type, result, path);
218 mpd_freeSong(edited);
222 * Checking the cache
223 * !!NEEDS TO BE THREAD SAFE !!
227 MetaDataResult meta_data_get_from_cache(mpd_Song *song, MetaDataType type, char **path)
229 if(!song)
231 return META_DATA_UNAVAILABLE;
233 /* Get values acording to type */
234 if(type == META_ALBUM_ART)
236 gchar *temp = NULL;
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);
243 q_free(temp);
244 if(*path)
246 /* if path length is NULL, then data unavailible */
247 if(strlen(*path) == 0)
249 q_free(*path);
251 *path = NULL;
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);
259 q_free(temp);
260 q_free(*path);
261 *path = NULL;
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)
272 if(!song->artist)
274 return META_DATA_UNAVAILABLE;
276 *path = cfg_get_single_value_as_string(cover_index,song->artist, "similar");
277 if(*path)
279 /* if path length is NULL, then data unavailible */
280 if(strlen(*path) == 0)
282 q_free(*path);
284 *path = NULL;
285 return META_DATA_UNAVAILABLE;
287 return META_DATA_AVAILABLE;
290 /* else default to fetching */
293 else if(type == META_ALBUM_TXT)
295 gchar *temp = NULL;
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);
302 q_free(temp);
303 if(*path)
305 /* if path length is NULL, then data unavailible */
306 if(strlen(*path) == 0)
308 q_free(*path);
309 *path = NULL;
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);
317 q_free(temp);
318 q_free(*path);
319 *path = NULL;
320 return META_DATA_FETCHING;
322 /* return that data is availible */
323 return META_DATA_AVAILABLE;
326 else if (type == META_ARTIST_ART)
328 gchar *temp = NULL;
329 if(!song->artist)
331 return META_DATA_UNAVAILABLE;
333 temp = g_strdup("image");
334 *path = cfg_get_single_value_as_string(cover_index,song->artist, temp);
335 q_free(temp);
336 if(*path)
338 /* if path length is NULL, then data unavailible */
339 if(strlen(*path) == 0)
341 q_free(*path);
342 *path = NULL;
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);
350 q_free(temp);
351 q_free(*path);
352 *path = NULL;
353 return META_DATA_FETCHING;
355 /* return that data is availible */
356 return META_DATA_AVAILABLE;
359 else if (type == META_ARTIST_TXT)
361 gchar *temp = NULL;
362 if(!song->artist)
364 return META_DATA_UNAVAILABLE;
366 temp = g_strdup("biography");
367 *path = cfg_get_single_value_as_string(cover_index,song->artist, temp);
368 q_free(temp);
369 if(*path)
371 /* if path length is NULL, then data unavailible */
372 if(strlen(*path) == 0)
374 q_free(*path);
375 *path = NULL;
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);
383 q_free(temp);
384 q_free(*path);
385 *path = NULL;
386 return META_DATA_FETCHING;
388 /* return that data is availible */
389 return META_DATA_AVAILABLE;
393 if(type == META_SONG_TXT)
395 gchar *temp = NULL;
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);
402 q_free(temp);
403 if(*path)
405 /* if path length is NULL, then data unavailible */
406 if(strlen(*path) == 0)
408 q_free(*path);
409 *path = NULL;
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);
417 q_free(temp);
418 q_free(*path);
419 *path = NULL;
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 */
442 if(data->id == 0)
444 debug_printf(DEBUG_INFO, "Quit command recieved.. quitting");
445 return;
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;
465 else
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)
472 * Start fetching
474 if(data->result == META_DATA_FETCHING)
476 char *path = NULL;
477 int i = 0;
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++)
492 /* *
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;
502 /**
503 * update cache
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
521 data = NULL;
522 g_mutex_unlock(meta_processing);
524 g_idle_add((GSourceFunc)meta_data_handle_results,NULL);
525 }while(1);
528 static gboolean meta_data_handle_results(void)
530 meta_thread_data *data = NULL;
531 int test = 0;
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);
545 if(data->callback)
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);
553 q_free(data);
556 * Keep the timer running
558 /* update when handled */
559 test = g_mutex_trylock(meta_processing);
560 if(test)
561 g_mutex_unlock(meta_processing);
562 gmpc_meta_watcher_queue_size_changed(gmw, q_async_queue_true_length(meta_commands)+!test, num_queries);
563 return FALSE;
567 * Initialize
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);
577 q_free(url);
578 url = gmpc_get_covers_path("covers.db");
579 cover_index = cfg_open(url);
580 q_free(url);
583 * The command queue
585 meta_commands = q_async_queue_new();
587 * the result queue
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)
601 int i=0;
602 int changed = FALSE;
603 meta_num_plugins++;
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;
608 do{
609 changed=0;
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];
615 changed=1;
616 meta_plugins[i] = meta_plugins[i+1];
617 meta_plugins[i+1] = temp;
620 }while(changed);
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);
630 return FALSE;
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);
644 meta_data_cleanup();
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;
656 if(meta_thread)
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);
665 q_free(mtd);
667 /* Create the quit signal, this is just an empty request with id 0 */
668 mtd = g_malloc0(sizeof(*mtd));
669 mtd->id = 0;
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);
676 /* cleanup */
677 g_free(mtd);
678 debug_printf(DEBUG_INFO,"Done..");
680 if(meta_commands)
681 g_mutex_free(meta_processing);
682 if(meta_commands)
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))
691 return TRUE;
692 if(!gmpc_meta_watcher_match_data(mt1->type&META_QUERY_DATA_TYPES, mt1->song, mt2->song))
694 return TRUE;
696 return FALSE;
699 * Function called by the "client"
701 MetaDataResult meta_data_get_path(mpd_Song *tsong, MetaDataType type, gchar **path,MetaDataCallback callback, gpointer data)
703 MetaDataResult ret;
704 meta_thread_data *mtd = NULL;
705 mpd_Song *song =NULL;
706 guint id = 0;
707 int test = 0;
708 /* TODO: Validate request */
711 * If there is no song
712 * return;
714 if(tsong == NULL)
716 return META_DATA_UNAVAILABLE;
720 * Check cache for result.
722 if(type&META_QUERY_NO_CACHE)
724 /* For others */
725 gmpc_meta_watcher_data_changed(gmw,tsong, (type)&META_QUERY_DATA_TYPES,META_DATA_FETCHING, NULL);
726 if(callback)
728 callback(song,META_DATA_FETCHING,NULL,data);
730 ret = META_DATA_FETCHING;
732 else
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)
741 return ret;
745 mtd = g_malloc0(sizeof(*mtd));
747 * Make a copy
749 mtd->edited = rewrite_mpd_song(tsong, type);
751 /**
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);
766 q_free(mtd);
767 return ret;
773 * If no result, start a thread and start fetching the data from there
778 * unique id
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);
784 mtd->type = type;
785 mtd->callback = callback;
786 mtd->data = data;
788 * Check if request is allready in queue
790 /* when using old api style, the request is commited anyway */
791 if(!callback)
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);
805 g_free(mtd);
806 return ret;
808 q_async_queue_unlock(meta_commands);
811 /** push it to the other thread */
813 num_queries ++;
814 /* I should fix this */
815 test = g_mutex_trylock(meta_processing);
816 if(test)
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 */
822 mtd = NULL;
824 return ret;