Remove some debug, improve some loading.
[gmpc.git] / src / MetaData / metadata.c
blobe2cb9fa7ad92efe71ddac0861325ff3e909f040f
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 "preferences.h"
29 #include "gmpc_easy_download.h"
31 #define LOG_DOMAIN "MetaData"
33 #include <glyr/glyr.h>
34 #include <glyr/cache.h>
36 int meta_num_plugins=0;
37 gmpcPluginParent **meta_plugins = NULL;
38 static void meta_data_sort_plugins(void);
42 /**
43 * GLYR
45 static GList *process_queue = NULL;
46 static GAsyncQueue *gaq = NULL;
47 static GAsyncQueue *return_queue = NULL;
48 static GlyrDatabase *db = NULL;
51 /**
52 * This queue is used to send replies back.
54 enum MTD_Action {
55 MTD_ACTION_QUERY_METADATA,
56 MTD_ACTION_CLEAR_ENTRY,
57 MTD_ACTION_SET_ENTRY,
58 MTD_ACTION_QUERY_LIST,
59 MTD_ACTION_QUIT
62 /**
63 * Structure holding a metadata query */
64 typedef struct {
65 /* The type of action todo */
66 enum MTD_Action action;
67 /* unique id for the query (unused)*/
68 guint id;
69 /* The callback to call when the query is done, or NULL */
70 MetaDataCallback callback;
71 /* Callback user_data pointer*/
72 gpointer data;
73 /* The song the data is queries for */
74 mpd_Song *song;
75 /* The type of metadata */
76 MetaDataType type;
77 /* Result */
78 MetaDataResult result;
79 /* The actual result data */
80 MetaData *met;
81 GList *met_results;
82 } meta_thread_data;
85 // Validate if enough information is available for the query.
86 static gboolean meta_data_validate_query(mpd_Song *tsong, MetaDataType type)
88 switch(type)
90 case META_GENRE_SIMILAR:
91 if(tsong->genre == NULL || tsong->genre[0] == '\0')
92 return FALSE;
93 break;
94 case META_SONG_GUITAR_TAB:
95 case META_SONG_TXT:
96 case META_SONG_SIMILAR:
97 if(tsong->title == NULL || tsong->title[0] == '\0')
98 return FALSE;
99 if(tsong->artist == NULL || tsong->artist[0] == '\0')
100 return FALSE;
101 break;
102 case META_ALBUM_ART:
103 case META_ALBUM_TXT:
104 if(tsong->album == NULL || tsong->album[0] == '\0')
105 return FALSE;
106 case META_ARTIST_TXT:
107 case META_ARTIST_SIMILAR:
108 case META_BACKDROP_ART:
109 case META_ARTIST_ART:
110 if(tsong->artist == NULL|| tsong->artist[0] == '\0')
111 return FALSE;
112 break;
113 // other items.
114 case META_QUERY_DATA_TYPES:
115 case META_QUERY_NO_CACHE:
116 default:
117 return FALSE;
120 return TRUE;
124 static void meta_thread_data_free(meta_thread_data *mtd)
126 /* Free the result data */
127 if(mtd->met)
128 meta_data_free(mtd->met);
129 if(mtd->met_results) {
130 g_list_foreach(mtd->met_results, (GFunc)meta_data_free, NULL);
131 g_list_free(mtd->met_results);
132 mtd->met_results = NULL;
134 /* Free the copie and edited version of the songs */
135 if(mtd->song)
136 mpd_freeSong(mtd->song);
138 /* Free the Request struct */
139 g_slice_free(meta_thread_data, mtd);
141 gboolean meta_compare_func(meta_thread_data *mt1, meta_thread_data *mt2);
142 //static gboolean meta_data_handle_results(void);
144 mpd_Song *rewrite_mpd_song(mpd_Song *tsong, MetaDataType type)
146 mpd_Song *edited = NULL;
147 /* If it is not a mpd got song */
148 if(tsong->file == NULL )
150 if(type&(META_ALBUM_ART|META_ALBUM_TXT) && tsong->artist && tsong->album)
152 MpdData *data2 = NULL;
153 mpd_database_search_start(connection, TRUE);
154 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ARTIST, tsong->artist);
155 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM, tsong->album);
156 data2 = mpd_database_search_commit(connection);
157 if(data2)
159 edited = data2->song;
160 data2->song = NULL;
161 mpd_data_free(data2);
163 else if(mpd_server_tag_supported(connection,MPD_TAG_ITEM_ALBUM_ARTIST) && tsong->albumartist)
165 mpd_database_search_start(connection, TRUE);
166 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM_ARTIST, tsong->albumartist);
167 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM, tsong->album);
168 data2 = mpd_database_search_commit(connection);
169 if(data2)
171 edited = data2->song;
172 data2->song = NULL;
173 mpd_data_free(data2);
179 else if(type&(META_ARTIST_ART|META_ARTIST_TXT) && tsong->artist)
181 MpdData *data2 = NULL;
182 mpd_database_search_start(connection, TRUE);
183 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ARTIST, tsong->artist);
184 data2 = mpd_database_search_commit(connection);
185 if(data2)
187 edited = data2->song;
188 data2->song = NULL;
189 mpd_data_free(data2);
193 if(!edited)
194 edited = mpd_songDup(tsong);
197 * Collections detection
198 * Only do this for album related queries.
200 if(type&(META_ALBUM_ART|META_ALBUM_TXT))
202 if(edited->albumartist)
204 if(edited->artist)
205 g_free(edited->artist);
206 edited->artist = g_strdup(edited->albumartist);
209 else if(edited->album && edited->file)
211 int i=0;
212 MpdData *data2;
213 char *dir = g_path_get_dirname(edited->file);
214 mpd_database_search_start(connection,TRUE);
215 mpd_database_search_add_constraint(connection, MPD_TAG_ITEM_ALBUM, edited->album);
218 data2 = mpd_database_search_commit(connection);
219 if(data2)
221 for(i=0;data2; data2 = mpd_data_get_next(data2))
223 if(strncasecmp(data2->song->file, dir, strlen(dir))==0)
225 /* Check for NULL pointers */
226 if(data2->song->artist && edited->artist)
228 if(strcmp(data2->song->artist, edited->artist))
229 i++;
234 if(i >=3)
236 if(edited->artist)
237 g_free(edited->artist);
238 edited->artist = g_strdup("Various Artists");
240 g_free(dir);
244 * Artist renaming, Clapton, Eric -> Eric Clapton
245 if(edited->artist && cfg_get_single_value_as_int_with_default(config, "metadata", "rename", FALSE))
247 gchar **str = g_strsplit(edited->artist, ",", 2);
249 if(str[0] && str[1]) {
250 g_free(edited->artist);
251 edited->artist = g_strdup_printf("%s %s", g_strstrip(str[1]), g_strstrip(str[0]));
253 g_strfreev(str);
254 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "string converted to: '%s'", edited->artist);
258 * Sanitize album name and so (remove () (
260 if(cfg_get_single_value_as_int_with_default(config, "metadata", "sanitize", TRUE))
262 if(edited->album)
264 int i,j=0,depth=0;
265 int length;
266 char *album = edited->album;
267 edited->album = g_malloc0((strlen(album)+1)*sizeof(char));
268 length = strlen(album);
269 for(i=0;i< length;i++)
271 if(album[i] == '(') depth++;
272 else if (album[i] == ')')depth--;
273 else if (depth == 0) {
274 edited->album[j] = album[i];
275 j++;
278 g_free(album);
279 /* Remove trailing and leading spaces */
280 edited->album = g_strstrip(edited->album);
282 if(edited->title)
284 int i,j=0,depth=0;
285 char *title = edited->title;
286 int length;
287 edited->title = g_malloc0((strlen(title)+1)*sizeof(char));
288 length = strlen(title);
289 for(i=0;i< length;i++)
291 if(title[i] == '(') depth++;
292 else if (title[i] == ')')depth--;
293 else if (depth == 0) {
294 edited->title[j] = title[i];
295 j++;
298 g_free(title);
299 /* Remove trailing and leading spaces */
300 edited->title = g_strstrip(edited->title);
306 return edited;
309 * If the metadata thread managed to handle a result this function
310 * Will call the callback (from the main (gtk) thread)
312 static gboolean glyr_return_queue(void *user_data)
315 meta_thread_data *mtd = (meta_thread_data*)g_async_queue_try_pop(return_queue);
316 if(mtd)
318 printf("Process results: %i\n", mtd->action);
319 if(mtd->action == MTD_ACTION_QUERY_METADATA)
321 gmpc_meta_watcher_data_changed(gmw,mtd->song, (mtd->type)&META_QUERY_DATA_TYPES, mtd->result,mtd->met);
322 if(mtd->callback)
324 mtd->callback(mtd->song, mtd->result, mtd->met, mtd->data);
326 } else if (mtd->action == MTD_ACTION_QUERY_LIST)
328 if(mtd->callback){
329 MetaDataListCallback cb = (MetaDataListCallback)mtd->callback;
330 // TODO: Update callback for current usecase.
331 cb(NULL, "", mtd->met_results, mtd->data);
332 // Send done.
333 cb(NULL, "", NULL, mtd->data);
335 }else if (mtd->action == MTD_ACTION_CLEAR_ENTRY) {
336 printf("Signal no longer available.\n");
337 // Signal that this item is now no longer available.
338 gmpc_meta_watcher_data_changed(gmw, mtd->song, (mtd->type)&META_QUERY_DATA_TYPES, META_DATA_UNAVAILABLE, NULL);
341 meta_thread_data_free(mtd);
342 return true;
344 return false;
347 static MetaDataContentType setup_glyr_query(GlyrQuery *query,
348 const meta_thread_data *mtd)
350 MetaDataContentType content_type = META_DATA_CONTENT_RAW;
351 MetaDataType type = mtd->type&META_QUERY_DATA_TYPES;
353 /* Force UTF 8 */
354 glyr_opt_force_utf8(query, TRUE);
356 glyr_opt_parallel(query, 1);
357 glyr_opt_number(query, 1);
358 // timeout 5 seconds.
359 glyr_opt_timeout(query, 5);
360 /* set metadata */
361 glyr_opt_artist(query,(char*)mtd->song->artist);
362 glyr_opt_album (query,(char*)mtd->song->album);
363 glyr_opt_title (query,(char*)mtd->song->title);
365 /* set default type */
366 glyr_opt_type(query, GLYR_GET_UNSURE);
368 if(type == META_ARTIST_ART )
370 glyr_opt_type(query, GLYR_GET_ARTIST_PHOTOS);
371 content_type = META_DATA_CONTENT_RAW;
373 else if (type == META_BACKDROP_ART)
375 glyr_opt_type(query, GLYR_GET_BACKDROPS);
376 content_type = META_DATA_CONTENT_RAW;
378 else if(type == META_ARTIST_TXT)
380 glyr_opt_lang_aware_only(query,TRUE);
381 glyr_opt_type(query, GLYR_GET_ARTISTBIO);
382 content_type = META_DATA_CONTENT_TEXT;
384 else if(type == META_ARTIST_SIMILAR)
386 glyr_opt_type(query, GLYR_GET_SIMILIAR_ARTISTS);
387 // cfg_* is no longer thread safe
388 glyr_opt_number(query,20);
389 content_type = META_DATA_CONTENT_TEXT;
391 else if(type == META_ALBUM_ART &&
392 mtd->song->album != NULL)
394 glyr_opt_type(query, GLYR_GET_COVERART);
395 content_type = META_DATA_CONTENT_RAW;
397 else if(type == META_ALBUM_TXT &&
398 mtd->song->album != NULL)
400 glyr_opt_type(query, GLYR_GET_ALBUM_REVIEW);
401 content_type = META_DATA_CONTENT_TEXT;
403 else if(type == META_SONG_TXT &&
404 mtd->song->title != NULL)
406 glyr_opt_type(query, GLYR_GET_LYRICS);
407 content_type = META_DATA_CONTENT_TEXT;
409 else if(type == META_SONG_SIMILAR &&
410 mtd->song->title != NULL)
412 glyr_opt_type(query, GLYR_GET_SIMILIAR_SONGS);
413 // cfg_* is no longer thread safe
414 glyr_opt_number(query,20);
416 else if (type == META_SONG_GUITAR_TAB &&
417 mtd->song->title)
419 glyr_opt_type(query, GLYR_GET_GUITARTABS);
420 content_type = META_DATA_CONTENT_TEXT;
422 else {
423 g_warning("Unsupported metadata type, or insufficient info");
425 return content_type;
428 * Convert cache to MetaData object.
430 static MetaData * glyr_get_similiar_song_names(GlyrMemCache * cache)
432 MetaData * mtd = NULL;
433 while(cache != NULL)
435 if(cache->data != NULL)
437 gchar ** split = g_strsplit(cache->data,"\n",0);
438 if(split != NULL && split[0] != NULL)
440 gchar * buffer;
441 if(!mtd) {
442 mtd = meta_data_new();
443 mtd->type = META_SONG_SIMILAR;
444 mtd->plugin_name = g_strdup(cache->prov);
445 mtd->content_type = META_DATA_CONTENT_TEXT_LIST;
446 mtd->size = 0;
449 buffer = g_strdup_printf("%s::%s",split[1],split[0]);
450 g_log(LOG_DOMAIN,G_LOG_LEVEL_DEBUG, "%s\n", buffer);
452 mtd->size++;
453 mtd->content = g_list_append((GList*) mtd->content, buffer);
454 g_strfreev(split);
457 cache = cache->next;
459 return mtd;
463 * Convert cache to MetaData object.
465 static MetaData * glyr_get_similiar_artist_names(GlyrMemCache * cache)
467 MetaData * mtd = NULL;
468 while(cache != NULL)
470 if(cache->data != NULL)
472 gchar ** split = g_strsplit(cache->data,"\n",0);
473 if(split != NULL)
475 if(!mtd) {
476 mtd = meta_data_new();
477 mtd->type = META_ARTIST_SIMILAR;
478 mtd->plugin_name = g_strdup(cache->prov);
479 mtd->content_type = META_DATA_CONTENT_TEXT_LIST;
480 mtd->size = 0;
482 mtd->size++;
483 mtd->content = g_list_append((GList*) mtd->content,
484 g_strdup((char *)split[0]));
485 g_strfreev(split);
488 cache = cache->next;
490 return mtd;
493 * Convert GLYR result into gmpc result
495 static gboolean process_glyr_result(GlyrMemCache *cache,
496 MetaDataContentType content_type,
497 meta_thread_data *mtd)
499 gboolean retv = FALSE;
500 // If more then one time called and we allready have a results
501 // Push it in the results list.
502 if(mtd->met != NULL) {
503 mtd->met_results = g_list_prepend(mtd->met_results, (void*)mtd->met);
505 mtd->result = META_DATA_UNAVAILABLE;
506 mtd->met = NULL;
507 if(cache == NULL) return retv;
508 if(cache->rating >= 0)
510 if(mtd->type == META_ARTIST_SIMILAR)
512 MetaData * cont = glyr_get_similiar_artist_names(cache);
513 if(cont != NULL)
515 (mtd->met) = cont;
516 mtd->result = META_DATA_AVAILABLE;
517 retv = TRUE;
520 else if (mtd->type == META_SONG_SIMILAR)
522 MetaData * cont;
523 cont = glyr_get_similiar_song_names(cache);
524 if (cont != NULL)
526 (mtd->met) = cont;
527 mtd->result = META_DATA_AVAILABLE;
528 retv = TRUE;
531 else
533 (mtd->met) = meta_data_new();
534 (mtd->met)->type = mtd->type;
535 if(cache->cached)
537 (mtd->met)->plugin_name = g_strdup_printf("%s (cached)", cache->prov);
538 }else{
539 (mtd->met)->plugin_name = g_strdup(cache->prov);
541 (mtd->met)->content_type = content_type;
543 (mtd->met)->content = g_malloc(cache->size+1);
544 ((char*)(mtd->met)->content)[cache->size] = 0;
545 memcpy((mtd->met)->content, cache->data, cache->size);
547 // Steal the data.
548 mtd->met->content = cache->data;
549 cache->data = NULL;
550 (mtd->met)->size = cache->size;
551 cache->size = 0;
552 mtd->result = META_DATA_AVAILABLE;
553 // found something.
554 retv = TRUE;
556 memcpy(&(mtd->met->md5sum), &(cache->md5sum), 16);
557 mtd->met->md5sum[16] = '\0';
558 }else {
559 // Explicitely not found.
560 printf("Cache sais empty\n");
561 retv = TRUE;
563 return retv;
566 static GlyrQuery *glyr_exit_handle = NULL;
567 static GStaticMutex exit_handle_lock = G_STATIC_MUTEX_INIT;
570 * Load a file from an URI
572 * @param mtd A meta_thread_data.
574 * Loads a file from a hard-drive.
576 * @returns nothing.
578 static GlyrMemCache *glyr_fetcher_thread_load_uri(meta_thread_data *mtd)
580 GlyrMemCache *cache = NULL;
581 const char *path = meta_data_get_uri(mtd->met);
582 gchar *scheme = g_uri_parse_scheme(path);
584 if(scheme == NULL || strcmp(scheme, "file") == 0)
586 char *content = NULL;
587 gsize length =0;
588 g_file_get_contents(path, &content,&length, NULL);
589 // set it to raw.
590 mtd->met->content_type = META_DATA_CONTENT_RAW;
591 cache = glyr_cache_new();
592 cache->dsrc = g_strdup(path);
593 glyr_cache_set_data(cache, content, length);
596 // Clean old content.
597 g_free(mtd->met->content);
598 mtd->met->content = g_memdup(content, length);
599 mtd->met->size = length;
600 content = NULL;
603 // Testing force image type.
604 if(mtd->met->type == META_ALBUM_ART || mtd->met->type == META_ARTIST_ART ||
605 mtd->met->type == META_BACKDROP_ART)
607 cache->is_image = TRUE;
608 cache->img_format = g_strdup("jpeg");
610 memcpy(&(mtd->met->md5sum), &(cache->md5sum), 16);
611 mtd->met->md5sum[16] = '\0';
614 g_free(scheme);
615 return cache;
618 static GlyrMemCache *glyr_fetcher_thread_load_raw(meta_thread_data *mtd)
620 GlyrMemCache *cache = NULL;
621 cache = glyr_cache_new();
622 glyr_cache_set_data(cache,
623 g_memdup(mtd->met->content, mtd->met->size),
624 mtd->met->size);
625 // Testing force image type.
626 if(mtd->met->type == META_ALBUM_ART || mtd->met->type == META_ARTIST_ART ||
627 mtd->met->type == META_BACKDROP_ART)
629 cache->is_image = TRUE;
630 cache->img_format = g_strdup("jpeg");
632 memcpy(mtd->met->md5sum, cache->md5sum, 16);
633 mtd->met->md5sum[16] = '\0';
634 return cache;
637 static GlyrMemCache *glyr_fetcher_thread_load_text(meta_thread_data *mtd)
639 GlyrMemCache *cache = NULL;
640 cache = glyr_cache_new();
641 glyr_cache_set_data(cache,
642 g_strdup(mtd->met->content),
643 -1);
644 memcpy(mtd->met->md5sum, cache->md5sum, 16);
645 mtd->met->md5sum[16] = '\0';
646 return cache;
649 * Thread that does the GLYR requests
651 static void glyr_fetcher_thread(void *user_data)
653 void *d;
654 GlyrQuery query;
656 g_static_mutex_lock(&exit_handle_lock);
657 while((d = g_async_queue_pop(gaq)))
659 meta_thread_data *mtd = (meta_thread_data*)d;
661 // Check if this is the quit command.
662 if(mtd->action == MTD_ACTION_QUIT) {
663 printf("Quitting....");
664 g_static_mutex_unlock(&exit_handle_lock);
665 /* Free the Request struct */
666 meta_thread_data_free(mtd);
667 return;
669 else if (mtd->action == MTD_ACTION_CLEAR_ENTRY)
671 GlyrMemCache *cache = NULL;
673 // Setup cancel lock
674 glyr_exit_handle = &query;
675 g_static_mutex_unlock(&exit_handle_lock);
677 /* Set up the query */
678 glyr_query_init(&query);
679 setup_glyr_query(&query, mtd);
680 glyr_opt_number(&query, 0);
681 /* Set some random settings */
682 glyr_opt_verbosity(&query,4);
684 // Delete existing entries.
685 glyr_db_delete(db, &query);
687 // Set dummy entry in cache, so we know
688 // we searched for this before.
689 cache = glyr_cache_new();
690 // TODO: Remove this randomize hack.
691 glyr_cache_set_data(cache,
692 g_strdup("GMPC Dummy"),
693 -1);
694 cache->rating = -1;
696 // Add dummy entry
697 printf("Inserting dummy item\n");
698 glyr_db_insert(db,&query, cache);
700 // Cleanup
701 if(cache)glyr_free_list(cache);
703 // Clear the query, and lock the handle again.
704 g_static_mutex_lock(&exit_handle_lock);
705 glyr_exit_handle = NULL;
706 glyr_query_destroy(&query);
708 printf("Push back result\n");
709 // Push back result, and tell idle handle to handle it.
710 g_async_queue_push(return_queue, mtd);
711 // invalidate pointer.
712 mtd = NULL;
713 // Schedule the result thread in idle time.
714 g_idle_add(glyr_return_queue, NULL);
716 else if (mtd->action == MTD_ACTION_QUERY_LIST)
718 // Check if this is thread safe.
719 const char *md = connection_get_music_directory();
720 GLYR_ERROR err = GLYRE_OK;
721 MetaDataContentType content_type = META_DATA_CONTENT_RAW;
722 GlyrMemCache *cache = NULL;
724 glyr_exit_handle = &query;
725 g_static_mutex_unlock(&exit_handle_lock);
727 printf("new style query\n");
729 glyr_query_init(&query);
732 if(md != NULL && md[0] != '\0'&& mtd->song->file != NULL)
734 char *path = g_build_filename(md, mtd->song->file, NULL);
735 glyr_opt_musictree_path(&query, path);
736 g_free(path);
740 /* Set up the query */
741 content_type = setup_glyr_query(&query, mtd);
743 /* Set some random settings */
744 glyr_opt_verbosity(&query,3);
746 /* Tell libglyr to automatically lookup before searching the web */
747 glyr_opt_lookup_db(&query, db);
749 /* Also tell it not to write newly found items to the db */
750 glyr_opt_db_autowrite(&query, FALSE);
751 /* We want many results */
752 glyr_opt_number(&query, 25);
754 /* get metadata */
755 cache = glyr_get(&query,&err,NULL);
757 if(cache != NULL)
759 GlyrMemCache *iter = cache;
760 while(iter){
761 process_glyr_result(iter,content_type, mtd);
762 iter = iter->next;
764 // Done. (most last item into list)
765 process_glyr_result(NULL,content_type, mtd);
767 // Cleanup
768 if(cache)glyr_free_list(cache);
770 // Clear the query, and lock the handle again.
771 g_static_mutex_lock(&exit_handle_lock);
772 glyr_exit_handle = NULL;
773 glyr_query_destroy(&query);
775 // Push back result, and tell idle handle to handle it.
776 g_async_queue_push(return_queue, mtd);
777 // invalidate pointer.
778 mtd = NULL;
779 // Schedule the result thread in idle time.
780 g_idle_add(glyr_return_queue, NULL);
782 // QUERY database
783 else if (mtd->action == MTD_ACTION_QUERY_METADATA)
785 // Check if this is thread safe.
786 const char *md = connection_get_music_directory();
787 GLYR_ERROR err = GLYRE_OK;
788 MetaDataContentType content_type = META_DATA_CONTENT_RAW;
789 GlyrMemCache *cache = NULL;
791 glyr_exit_handle = &query;
792 g_static_mutex_unlock(&exit_handle_lock);
794 printf("new style query\n");
796 glyr_query_init(&query);
799 if(md != NULL && md[0] != '\0'&& mtd->song->file != NULL)
801 char *path = g_build_filename(md, mtd->song->file, NULL);
802 glyr_opt_musictree_path(&query, path);
803 g_free(path);
807 /* Set up the query */
808 content_type = setup_glyr_query(&query, mtd);
810 /* If cache disabled, remove the entry in the db */
811 if(((mtd->type)&META_QUERY_NO_CACHE) == META_QUERY_NO_CACHE)
813 printf("Disable cache\n");
814 glyr_opt_from(&query, "all;-local");
815 // Remove cache request.
816 mtd->type&=META_QUERY_DATA_TYPES;
817 // Delete the entry.
818 glyr_db_delete(db, &query);
821 /* Set some random settings */
822 glyr_opt_verbosity(&query,3);
824 /* Tell libglyr to automatically lookup before searching the web */
825 glyr_opt_lookup_db(&query, db);
827 /* Also tell it to write newly found items to the db */
828 glyr_opt_db_autowrite(&query, TRUE);
831 /* get metadata */
832 cache = glyr_get(&query,&err,NULL);
834 if(cache == NULL){
835 // Set dummy entry in cache, so we know
836 // we searched for this before.
837 cache = glyr_cache_new();
838 // TODO: Remove this randomize hack.
839 glyr_cache_set_data(cache,
840 g_strdup("GMPC Dummy"),
841 -1);
842 cache->rating = -1;
844 glyr_db_insert(db,&query, cache);
845 printf("Cache is Empty\n");
846 // Set unavailable
847 mtd->result = META_DATA_UNAVAILABLE;
848 }else{
849 process_glyr_result(cache,content_type, mtd);
851 // Cleanup
852 if(cache)glyr_free_list(cache);
854 // Clear the query, and lock the handle again.
855 g_static_mutex_lock(&exit_handle_lock);
856 glyr_exit_handle = NULL;
857 glyr_query_destroy(&query);
859 // Push back result, and tell idle handle to handle it.
860 g_async_queue_push(return_queue, mtd);
861 // invalidate pointer.
862 mtd = NULL;
863 // Schedule the result thread in idle time.
864 g_idle_add(glyr_return_queue, NULL);
866 else if (mtd->action == MTD_ACTION_SET_ENTRY)
868 GlyrMemCache *cache = NULL;
870 // Setup cancel lock
871 glyr_exit_handle = &query;
872 g_static_mutex_unlock(&exit_handle_lock);
874 /* Set up the query */
875 glyr_query_init(&query);
876 setup_glyr_query(&query, mtd);
877 glyr_opt_number(&query, 0);
878 /* Set some random settings */
879 glyr_opt_verbosity(&query,3);
881 // Delete existing entries.
882 glyr_db_delete(db, &query);
884 glyr_query_init(&query);
885 setup_glyr_query(&query, mtd);
886 glyr_opt_number(&query, 0);
887 /* Set some random settings */
888 glyr_opt_verbosity(&query,3);
889 // load data
890 if(meta_data_is_uri(mtd->met))
892 // try to load cache from file.
893 cache = glyr_fetcher_thread_load_uri(mtd);
895 else if (meta_data_is_raw(mtd->met))
897 // try to load cache from raw.
898 cache = glyr_fetcher_thread_load_raw(mtd);
899 } else if (meta_data_is_text(mtd->met) || meta_data_is_html(mtd->met))
901 // try to load cache from text.
902 cache = glyr_fetcher_thread_load_text(mtd);
905 // Cache.
906 if(cache)
908 cache->rating = 9;
909 printf("Do DB insert\n");
910 glyr_db_insert(db,&query, cache);
913 // Clear the query, and lock the handle again.
914 g_static_mutex_lock(&exit_handle_lock);
915 glyr_exit_handle = NULL;
916 glyr_query_destroy(&query);
918 // Push back result, and tell idle handle to handle it.
919 // set it to query metadata to get the right handle behaviour.
920 mtd->action = MTD_ACTION_QUERY_METADATA;
921 g_async_queue_push(return_queue, mtd);
922 // invalidate pointer.
923 mtd = NULL;
926 // Schedule the result thread in idle time.
927 g_idle_add(glyr_return_queue, NULL);
928 }else {
929 g_error("Unknown type of query to perform");
930 return;
936 * Initialize
938 GThread *gaq_fetcher_thread = NULL;
939 void meta_data_init(void)
941 gchar *url;
943 /* Is this function thread safe? */
944 url = gmpc_get_covers_path("");
946 //g_mutex_init(&exit_handle_lock);
947 /* Initialize..*/
948 printf("open glyr db: %s\n", url);
949 glyr_init();
950 db = glyr_db_init(url);
951 g_free(url);
954 gaq = g_async_queue_new();
955 return_queue = g_async_queue_new();
956 #if GLIB_CHECK_VERSION(2, 31, 0)
957 gaq_fetcher_thread = g_thread_new("Glyr thread fetch", (GThreadFunc)glyr_fetcher_thread, NULL);
958 #else
959 gaq_fetcher_thread = g_thread_create(glyr_fetcher_thread, NULL, TRUE, NULL);
960 #endif
964 void meta_data_add_plugin(gmpcPluginParent *plug)
966 g_assert(plug != NULL);
968 meta_num_plugins++;
969 meta_plugins = g_realloc(meta_plugins,(meta_num_plugins+1)*sizeof(gmpcPluginParent **));
970 meta_plugins[meta_num_plugins-1] = plug;
971 meta_plugins[meta_num_plugins] = NULL;
972 meta_data_sort_plugins();
975 static void meta_data_sort_plugins(void)
977 int i;
978 int changed = FALSE;
979 do{
980 changed=0;
981 for(i=0; i< (meta_num_plugins-1);i++)
983 if(gmpc_plugin_metadata_get_priority(meta_plugins[i]) > gmpc_plugin_metadata_get_priority(meta_plugins[i+1]))
985 gmpcPluginParent *temp = meta_plugins[i];
986 changed=1;
987 meta_plugins[i] = meta_plugins[i+1];
988 meta_plugins[i+1] = temp;
991 }while(changed);
994 static gboolean meta_data_check_plugin_changed_message(gpointer data)
996 playlist3_show_error_message(_("A new metadata plugin was added, gmpc has purged all failed hits from the cache"), ERROR_INFO);
997 return FALSE;
999 void meta_data_check_plugin_changed(void)
1001 int old_amount= cfg_get_single_value_as_int_with_default(config, "metadata", "num_plugins", 0);
1002 if(old_amount < meta_num_plugins)
1004 gtk_init_add(meta_data_check_plugin_changed_message, NULL);
1005 //metadata_cache_cleanup();
1007 if(old_amount != meta_num_plugins)
1009 cfg_set_single_value_as_int(config, "metadata", "num_plugins", meta_num_plugins);
1013 * TODO: Can we guarantee that all the downloads are stopped?
1015 void meta_data_destroy(void)
1017 meta_thread_data *mtd = NULL;
1019 * Clear the request queue, and tell thread to quit
1021 g_async_queue_lock(gaq);
1022 while((mtd = g_async_queue_try_pop_unlocked(gaq))){
1023 /* Free */
1024 meta_thread_data_free(mtd);
1026 mtd = g_slice_new0(meta_thread_data);//g_malloc0(sizeof(*mtd));
1027 mtd->action = MTD_ACTION_QUIT;
1028 g_async_queue_push_unlocked(gaq, mtd);
1029 mtd = NULL;
1030 g_async_queue_unlock(gaq);
1031 // add lock?
1032 g_static_mutex_lock(&exit_handle_lock);
1033 if(glyr_exit_handle) {
1034 printf("Sending quit signal\n");
1035 glyr_signal_exit(glyr_exit_handle);
1037 g_static_mutex_unlock(&exit_handle_lock);
1039 printf("Waiting for glyr to finish.....\n");
1040 g_thread_join(gaq_fetcher_thread);
1041 //g_mutex_clear(&exit_handle_lock);
1043 glyr_db_destroy(db);
1044 glyr_cleanup();
1046 * Wait for thread to quit
1048 g_async_queue_lock(return_queue);
1049 while((mtd = g_async_queue_try_pop_unlocked(return_queue))){
1050 /* Free any possible plugin results */
1051 meta_thread_data_free(mtd);
1053 g_async_queue_unlock(return_queue);
1054 g_async_queue_unref(gaq);
1055 g_async_queue_unref(return_queue);
1058 gboolean meta_compare_func(meta_thread_data *mt1, meta_thread_data *mt2)
1060 if(mt1->action != mt2->action) return TRUE;
1062 if((mt1->type&META_QUERY_DATA_TYPES) != (mt2->type&META_QUERY_DATA_TYPES))
1063 return TRUE;
1064 if(!gmpc_meta_watcher_match_data(mt1->type&META_QUERY_DATA_TYPES, mt1->song, mt2->song))
1066 return TRUE;
1068 return FALSE;
1072 static int test_id = 0;
1074 void meta_data_set_entry ( mpd_Song *song, MetaData *met )
1076 meta_thread_data *mtd = NULL;
1077 if(song == NULL || met == NULL || !meta_data_validate_query(song, met->type))
1079 g_warning("Trying to set metadata entry with insufficient information");
1080 return;
1083 mtd = g_slice_new0(meta_thread_data);
1084 mtd->action = MTD_ACTION_SET_ENTRY;
1085 mtd->id = ++test_id;
1086 /* Create a copy of the original song */
1087 mtd->song = rewrite_mpd_song(song, met->type);
1088 /* Set the type */
1089 mtd->type = met->type;
1090 /* set result NULL */
1091 mtd->met = meta_data_dup(met);;
1092 /* signal we are fetching. */
1093 gmpc_meta_watcher_data_changed(gmw,mtd->song, (mtd->type)&META_QUERY_DATA_TYPES, META_DATA_FETCHING,NULL);
1094 /* Set entry */
1095 printf("Request setting entry\n");
1096 g_async_queue_push(gaq, mtd);
1097 mtd = NULL;
1100 void meta_data_clear_entry(mpd_Song *song, MetaDataType type)
1102 meta_thread_data *mtd = NULL;
1103 if(!meta_data_validate_query(song, type))
1105 g_warning("Trying to clear metadata entry with insufficient information");
1106 return;
1108 mtd = g_slice_new0(meta_thread_data);
1109 mtd->action = MTD_ACTION_CLEAR_ENTRY;
1110 mtd->id = ++test_id;
1111 /* Create a copy of the original song */
1112 //mtd->song = mpd_songDup(song);
1113 mtd->song = rewrite_mpd_song(song, type);
1114 /* Set the type */
1115 mtd->type = type;
1116 /* set result NULL */
1117 mtd->met = NULL;
1118 printf("Request clearing entry\n");
1119 g_async_queue_push(gaq, mtd);
1120 mtd = NULL;
1124 * Function called by the "client"
1127 MetaDataResult meta_data_get_path(mpd_Song *tsong, MetaDataType type, MetaData **met,MetaDataCallback callback, gpointer data)
1129 meta_thread_data *mtd = NULL;
1131 if(!meta_data_validate_query(tsong, type))
1133 *met = NULL;
1134 return META_DATA_UNAVAILABLE;
1138 * unique id
1139 * Not needed, but can be usefull for debugging
1142 //mtd = g_malloc0(sizeof(*mtd));
1143 mtd = g_slice_new0(meta_thread_data);//g_malloc0(sizeof(*mtd));
1144 mtd->action = MTD_ACTION_QUERY_METADATA;
1145 mtd->id = ++test_id;
1146 /* Create a copy of the original song */
1147 mtd->song = rewrite_mpd_song(tsong, type);
1148 /* Set the type */
1149 mtd->type = type;
1150 /* the callback */
1151 mtd->callback = callback;
1152 /* the callback data */
1153 mtd->data = data;
1154 /* Set that we are fetching */
1155 mtd->result = META_DATA_FETCHING;
1156 /* set result NULL */
1157 mtd->met = NULL;
1159 * If requested query the cache first
1161 if((type&META_QUERY_NO_CACHE) == 0)
1163 const char *md = connection_get_music_directory();
1164 GLYR_ERROR err = GLYRE_OK;
1165 MetaDataResult mrd;
1166 MetaDataContentType content_type = META_DATA_CONTENT_RAW;
1167 GlyrQuery query;
1168 GlyrMemCache * cache = NULL;
1170 glyr_query_init(&query);
1171 /* Set some random settings */
1172 glyr_opt_verbosity(&query,3);
1174 if(md != NULL && md[0] != '\0' && mtd->song->file != NULL)
1176 char *path = g_build_filename(md, mtd->song->file, NULL);
1177 glyr_opt_musictree_path(&query, path);
1178 g_free(path);
1181 content_type = setup_glyr_query(&query, mtd);
1182 cache = glyr_db_lookup(db, &query);
1183 if(process_glyr_result(cache,content_type, mtd))
1185 // Cleanup
1186 if(cache)glyr_free_list(cache);
1187 glyr_query_destroy(&query);
1189 mrd = mtd->result;
1190 *met = mtd->met;
1191 mtd->met = NULL;
1192 // Free mtd
1193 meta_thread_data_free(mtd);
1195 return mrd;
1197 if(cache)glyr_free_list(cache);
1198 glyr_query_destroy(&query);
1200 else
1202 gmpc_meta_watcher_data_changed(gmw,mtd->song, (mtd->type)&META_QUERY_DATA_TYPES, META_DATA_FETCHING,NULL);
1203 if(mtd->callback)
1205 mtd->callback(mtd->song, META_DATA_FETCHING, NULL, mtd->data);
1208 printf("start query\n");
1211 g_async_queue_push(gaq, mtd);
1212 mtd = NULL;
1213 return META_DATA_FETCHING;
1216 static gchar * strip_invalid_chars(gchar *input)
1218 int i = 0;
1219 int length = 0;
1220 g_assert(input != NULL);
1221 length = strlen(input);
1222 if(input == NULL) return NULL;
1223 for(i=0;i<length;i++)
1225 switch(input[i])
1227 case ':':
1228 case '\\':
1229 case '/':
1230 case ';':
1231 case '*':
1232 case '?':
1233 case '\"':
1234 case '<':
1235 case '>':
1236 case '|':
1237 input[i] = ' ';
1238 default:
1239 break;
1242 return input;
1245 * Helper function for storing metadata
1247 gchar * gmpc_get_metadata_filename(MetaDataType type, mpd_Song *song, char *ext)
1249 gchar *retv= NULL;
1250 /* home dir */
1251 const gchar *homedir = g_get_user_cache_dir();
1252 g_assert(song->artist != NULL);
1253 g_assert(type < META_QUERY_DATA_TYPES);
1256 GError *error = NULL;
1257 gchar *filename = NULL, *dirname = NULL;
1258 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);
1260 /* Convert it so the filesystem likes it */
1261 /* TODO: Add error checking */
1263 dirname = g_filename_from_utf8(song->artist, -1, NULL, NULL, NULL);
1265 if (g_get_charset (&charset))
1267 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Locale is utf-8, just copying");
1268 dirname = g_strdup(song->artist);
1269 }else{
1270 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Locale is %s converting to UTF-8", charset);
1271 dirname = g_convert_with_fallback (song->artist, -1,
1272 charset, "UTF-8","-", NULL, NULL, &error);
1275 if(dirname == NULL)
1277 const gchar *charset;
1278 g_get_charset (&charset);
1279 dirname = g_convert_with_fallback (song->artist, -1,
1280 charset, "UTF-8",(char *)"-", NULL, NULL, &error);
1282 //dirname = g_filename_from_utf8(song->artist,-1,NULL,NULL,&error);
1283 if(error) {
1284 g_log(LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Failed to convert %s to file encoding. '%s'", song->artist, error->message);
1285 g_error_free(error);
1286 if(dirname) g_free(dirname);
1287 dirname = g_strdup("invalid");
1289 dirname = strip_invalid_chars(dirname);
1290 retv = g_build_path(G_DIR_SEPARATOR_S, homedir,"gmpc","metadata", dirname,NULL);
1291 if(g_file_test(retv, G_FILE_TEST_EXISTS) == FALSE) {
1292 if(g_mkdir_with_parents(retv, 0755) < 0) {
1293 g_error("Failed to create: %s\n", retv);
1294 abort();
1297 if(!g_file_test(retv, G_FILE_TEST_IS_DIR)) {
1298 g_error("File exists but is not a directory: %s\n", retv);
1299 abort();
1301 g_free(retv);
1302 if(type&(META_ALBUM_ART|META_ALBUM_TXT)) {
1303 gchar *temp ;
1304 g_assert(song->album != NULL);
1305 temp =g_filename_from_utf8(song->album,-1,NULL,NULL,NULL);
1306 filename = g_strdup_printf("%s.%s", temp,extension);
1307 g_free(temp);
1308 }else if(type&META_ARTIST_ART){
1309 filename = g_strdup_printf("artist_IMAGE.%s", extension);
1310 }else if (type&META_ARTIST_TXT){
1311 filename = g_strdup_printf("artist_BIOGRAPHY.%s", extension);
1312 }else if (type&META_SONG_TXT) {
1313 gchar *temp ;
1314 g_assert(song->title != NULL);
1315 temp =g_filename_from_utf8(song->title,-1,NULL,NULL,NULL);
1316 filename = g_strdup_printf("%s_LYRIC.%s", temp,extension);
1317 g_free(temp);
1318 }else if (type&META_SONG_GUITAR_TAB) {
1319 gchar *temp ;
1320 g_assert(song->title != NULL);
1321 temp =g_filename_from_utf8(song->title,-1,NULL,NULL,NULL);
1322 filename = g_strdup_printf("%s_GUITAR_TAB.%s", temp,extension);
1323 g_free(temp);
1325 filename = strip_invalid_chars(filename);
1326 retv = g_build_path(G_DIR_SEPARATOR_S, homedir,"gmpc", "metadata", dirname,filename,NULL);
1327 if(filename) g_free(filename);
1328 if(dirname) g_free(dirname);
1330 return retv;
1332 static void metadata_pref_priority_changed(GtkCellRenderer *renderer, char *path, char *new_text, GtkListStore *store)
1334 GtkTreeIter iter;
1335 if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path))
1337 gmpcPluginParent *plug;
1338 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 0, &plug, -1);
1339 if(plug)
1341 gmpc_plugin_metadata_set_priority(plug,(gint)g_ascii_strtoull(new_text, NULL, 0));
1342 gtk_list_store_set(GTK_LIST_STORE(store), &iter, 2, gmpc_plugin_metadata_get_priority(plug),-1);
1343 meta_data_sort_plugins();
1348 * Get the enabled state directly from the plugin
1350 static void __column_data_func_enabled(GtkTreeViewColumn *column,
1351 GtkCellRenderer *cell,
1352 GtkTreeModel *model,
1353 GtkTreeIter *iter,
1354 gpointer data)
1356 gmpcPluginParent *plug;
1357 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, 0, &plug, -1);
1358 if(plug)
1360 gboolean active = gmpc_plugin_get_enabled(plug);
1361 g_object_set(G_OBJECT(cell), "active", active, NULL);
1365 * Set enabled
1367 static void __column_toggled_enabled(GtkCellRendererToggle *renderer,
1368 char *path,
1369 gpointer store)
1371 GtkTreeIter iter;
1372 if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path))
1374 gmpcPluginParent *plug;
1375 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 0, &plug, -1);
1376 if(plug)
1378 gboolean state = !gtk_cell_renderer_toggle_get_active(renderer);
1379 gmpc_plugin_set_enabled(plug,state);
1380 preferences_window_update();
1384 static void metadata_construct_pref_pane(GtkWidget *container)
1386 GtkObject *adjustment;
1387 int i = 0;
1388 GtkCellRenderer *renderer;
1389 GtkWidget *vbox, *sw;
1390 GtkWidget *treeview;
1391 GtkWidget *label = NULL;
1392 GtkListStore *store = gtk_list_store_new(3,
1393 G_TYPE_POINTER, /* The GmpcPlugin */
1394 G_TYPE_STRING, /* Name */
1395 G_TYPE_INT /* The priority */
1397 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
1398 2, GTK_SORT_ASCENDING);
1401 /* Create vbox */
1402 vbox = gtk_vbox_new(FALSE, 6);
1403 /* tree + container */
1404 sw = gtk_scrolled_window_new(NULL, NULL);
1405 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
1406 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1407 treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1408 gtk_container_add(GTK_CONTAINER(sw), treeview);
1411 /* enable column */
1412 renderer = gtk_cell_renderer_toggle_new();
1413 gtk_tree_view_insert_column_with_data_func(GTK_TREE_VIEW(treeview),
1415 "Enabled",
1416 renderer,
1417 (GtkTreeCellDataFunc)__column_data_func_enabled,
1418 NULL,
1419 NULL);
1420 g_object_set(G_OBJECT(renderer), "activatable", TRUE, NULL);
1421 g_signal_connect(G_OBJECT(renderer), "toggled" ,
1422 G_CALLBACK(__column_toggled_enabled), store);
1424 /* Build the columns */
1425 renderer = gtk_cell_renderer_text_new();
1426 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),
1428 "Name",
1429 renderer,
1430 "text", 1,
1431 NULL);
1432 renderer = gtk_cell_renderer_spin_new();
1434 adjustment = gtk_adjustment_new (0, 0, 100, 1, 0, 0);
1435 g_object_set(G_OBJECT(renderer), "editable", TRUE, NULL);
1436 g_object_set (renderer, "adjustment", adjustment, NULL);
1437 g_signal_connect(G_OBJECT(renderer), "edited", G_CALLBACK(metadata_pref_priority_changed), store);
1438 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),
1440 "Priority",
1441 renderer,
1442 "text", 2,
1443 NULL);
1446 /* Add the list to the vbox */
1447 gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
1449 gtk_container_add(GTK_CONTAINER(container), vbox);
1450 /* add plugins to list */
1451 for(i=0; i< meta_num_plugins;i++)
1453 GtkTreeIter iter;
1454 gtk_list_store_insert_with_values(store, &iter, -1,
1455 0, meta_plugins[i],
1456 1, gmpc_plugin_get_name(meta_plugins[i]),
1457 2, gmpc_plugin_metadata_get_priority(meta_plugins[i]),
1458 -1);
1461 label = gtk_label_new("Plugins are evaluated from low priority to high");
1462 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
1463 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1464 gtk_widget_show_all(container);
1466 static void metadata_destroy_pref_pane(GtkWidget *container)
1468 GtkWidget *child = gtk_bin_get_child(GTK_BIN(container));
1469 if(child)
1471 gtk_widget_destroy(child);
1475 void metadata_get_list_cancel(gpointer data)
1479 gpointer metadata_get_list(mpd_Song *song, MetaDataType type, void (*callback)(gpointer handle,const gchar *plugin_name, GList *list, gpointer data), gpointer data)
1481 meta_thread_data *mtd = NULL;
1483 INIT_TIC_TAC()
1486 * unique id
1487 * Not needed, but can be usefull for debugging
1490 //mtd = g_malloc0(sizeof(*mtd));
1491 mtd = g_slice_new0(meta_thread_data);//g_malloc0(sizeof(*mtd));
1492 mtd->action = MTD_ACTION_QUERY_LIST;
1493 mtd->id = ++test_id;
1494 /* Create a copy of the original song */
1495 mtd->song = rewrite_mpd_song(song, type);
1496 /* Set the type */
1497 mtd->type = type;
1498 /* the callback */
1499 mtd->callback = callback;
1500 /* the callback data */
1501 mtd->data = data;
1502 /* Set that we are fetching */
1503 mtd->result = META_DATA_FETCHING;
1504 /* set result NULL */
1505 mtd->met = NULL;
1506 mtd->met_results = NULL;
1507 printf("start query\n");
1509 TEC("Pushing actual query");
1511 g_async_queue_push(gaq, mtd);
1512 mtd = NULL;
1513 return NULL;
1515 // callback(NULL, NULL, NULL, data);
1516 // return NULL;
1517 #if 0
1518 MLQuery *q = g_malloc0(sizeof(*q));
1519 q->cancel =FALSE;
1520 q->callback = callback;
1521 q->userdata = data;
1522 q->type = type;
1523 q->calls =1;
1525 * Create a copy, so song is guarantee to be valid during queries of plugins
1527 q->song = mpd_songDup(song);
1528 /* Check cache */
1530 MetaData *met = NULL;
1531 int retv = meta_data_get_from_cache(q->song, q->type,&met);
1532 g_log("MetaData", G_LOG_LEVEL_DEBUG, "Queried cache: %i",retv);
1533 if(retv == META_DATA_AVAILABLE)
1535 GList *list = g_list_append(NULL, met);
1537 g_log("MetaData", G_LOG_LEVEL_DEBUG, "Callback");
1538 q->callback(q, met->plugin_name,list, q->userdata);
1539 g_log("MetaData", G_LOG_LEVEL_DEBUG, "Cleanup");
1540 g_list_foreach(g_list_first(list),(GFunc) meta_data_free, NULL);
1541 g_list_free(list);
1542 g_log("MetaData", G_LOG_LEVEL_DEBUG, "Cleanup done");
1543 met = NULL;
1545 if(met)
1546 meta_data_free(met);
1549 g_log("MetaData", G_LOG_LEVEL_DEBUG, "Start first itteration idle");
1550 g_idle_add(metadata_get_list_itterate_idle, q);
1551 return q;
1552 #endif
1555 * MetaData
1557 MetaData *meta_data_new(void)
1559 /* Create a new structure completely filled with 0's */
1560 MetaData *retv = g_new0(MetaData, 1);
1561 retv->content_type = META_DATA_CONTENT_EMPTY;
1562 return retv;
1565 void meta_data_free(MetaData *data)
1567 if(data == NULL) return;
1568 if(data->content) {
1569 if(data->content_type == META_DATA_CONTENT_TEXT_VECTOR)
1571 g_strfreev(data->content);
1573 else if (data->content_type == META_DATA_CONTENT_TEXT_LIST)
1575 g_list_foreach((GList *)data->content, (GFunc)g_free, NULL);
1576 g_list_free((GList *)data->content);
1578 else if(data->content_type != META_DATA_CONTENT_EMPTY)
1579 g_free(data->content);
1580 data->content = NULL;
1581 data->size = 0;
1583 if(data->thumbnail_uri) g_free(data->thumbnail_uri);
1584 data->thumbnail_uri = NULL;
1585 if(data->plugin_name) g_free(data->plugin_name);
1587 g_free(data);
1590 MetaData *meta_data_dup(MetaData *data)
1592 MetaData *retv = meta_data_new();
1593 g_assert(data != NULL);
1594 /* Copy type of metadata */
1595 retv->type = data->type;
1596 /* Copy the type of the data */
1597 retv->content_type = data->content_type;
1598 /* Copy the name of the providing plugin. (const char * so only copy pointer ) */
1599 retv->plugin_name = g_strdup(data->plugin_name);
1600 /* copy the content */
1601 retv->size = data->size;
1602 if(retv->content_type == META_DATA_CONTENT_TEXT_VECTOR) {
1603 if(data->content) retv->content =(void *) g_strdupv((gchar **)data->content);
1605 /* raw data always needs a length */
1606 else if (data->content_type == META_DATA_CONTENT_RAW)
1608 if(data->size > 0 ) {
1609 retv->content = g_memdup(data->content, (guint)data->size);
1612 else if (data->content_type == META_DATA_CONTENT_TEXT_LIST)
1614 GList *list = NULL;
1615 GList *iter = g_list_first((GList *)(data->content));
1616 while((iter = g_list_next(iter)))
1618 list = g_list_append(list, g_strdup(iter->data));
1620 data->content =(void *) g_list_reverse(list);
1622 else if (data->content_type == META_DATA_CONTENT_EMPTY)
1624 retv->content = NULL; retv->size = 0;
1626 /* Text is NULL terminated */
1627 else
1629 retv->content = NULL;
1630 if(data->content){
1631 retv->content = g_strdup((gchar *)data->content);
1634 if(data->thumbnail_uri != NULL) {
1635 retv->thumbnail_uri = g_strdup(data->thumbnail_uri);
1638 memcpy(&(retv->md5sum),&(data->md5sum), 16);
1639 retv->md5sum[16] = '\0';
1641 return retv;
1643 MetaData *meta_data_dup_steal(MetaData *data)
1645 MetaData *retv = meta_data_new();
1646 g_assert(data != NULL);
1647 /* Copy type of metadata */
1648 retv->type = data->type;
1649 /* Copy the type of the data */
1650 retv->content_type = data->content_type;
1651 /* Copy the name of the providing plugin. (const char * so only copy pointer ) */
1652 retv->plugin_name = g_strdup(data->plugin_name);
1653 /* copy the content */
1654 retv->size = data->size;
1655 retv->content = data->content;
1656 data->size = 0;
1657 data->content = NULL;
1658 retv->thumbnail_uri = data->thumbnail_uri;
1659 data->thumbnail_uri = NULL;
1661 memcpy(&(retv->md5sum),&(data->md5sum), 16);
1662 retv->md5sum[16] = '\0';
1664 return retv;
1666 gboolean meta_data_is_empty(const MetaData *data)
1668 return data->content_type == META_DATA_CONTENT_EMPTY;
1670 gboolean meta_data_is_uri(const MetaData *data)
1672 return data->content_type == META_DATA_CONTENT_URI;
1674 const gchar * meta_data_get_uri(const MetaData *data)
1676 g_assert(meta_data_is_uri(data));
1677 return (const gchar *)data->content;
1679 void meta_data_set_uri(MetaData *data, const gchar *uri)
1681 g_assert(meta_data_is_uri(data));
1682 if(data->content) g_free(data->content);
1683 data->content = g_strdup(uri);
1685 void meta_data_set_raw(MetaData *item, guchar *data, gsize len)
1687 g_assert(meta_data_is_raw(item));
1688 if(item->content) g_free(item->content);
1689 item->content = g_memdup(data, len);
1690 item->size = len;
1692 void meta_data_set_raw_owned(MetaData *item, guchar **data, gsize *len)
1694 g_assert(meta_data_is_raw(item));
1695 if(item->content) g_free(item->content);
1696 item->content = *data;
1697 *data = NULL;
1698 item->size = *len;
1699 *len = 0;
1701 void meta_data_set_thumbnail_uri(MetaData *data, const gchar *uri)
1703 g_assert(meta_data_is_uri(data));
1704 if(data->thumbnail_uri) g_free(data->thumbnail_uri);
1705 data->thumbnail_uri = g_strdup(uri);
1707 const gchar * meta_data_get_thumbnail_uri(const MetaData *data)
1709 g_assert(meta_data_is_uri(data));
1710 /* Only valid for images. */
1711 g_assert((data->type&(META_ALBUM_ART|META_ARTIST_ART)) != 0);
1713 return (const gchar *)data->thumbnail_uri;
1716 gboolean meta_data_is_text(const MetaData *data)
1718 return data->content_type == META_DATA_CONTENT_TEXT;
1721 void meta_data_set_text(MetaData *data, const gchar *text)
1723 if(meta_data_is_text(data))
1725 if(data->content) g_free(data->content);
1726 data->content = g_strdup(text);
1727 data->size = -1;
1730 const gchar * meta_data_get_text(const MetaData *data)
1732 g_assert(meta_data_is_text(data));
1733 return (const gchar *)data->content;
1736 gboolean meta_data_is_html(const MetaData *data)
1738 return data->content_type == META_DATA_CONTENT_HTML;
1740 const gchar * meta_data_get_html(const MetaData *data)
1742 g_assert(meta_data_is_html(data));
1743 return (const gchar *)data->content;
1748 * Convert a html encoded token (like &amp; and &#030;
1749 * to the corresponding unichar.
1751 * On early falire (i.e. empty string) returns 0.
1752 * If the encoding is wrong, it returns the unicode error value 0xFFFC.
1754 static gunichar htmlname2unichar( const gchar* htmlname, gint len )
1756 // * can be optimized by sorting
1757 // * and checking str(n)cmp results
1758 static struct _TT {
1759 const gchar *str;
1760 gint utf8;
1762 html2utf8_table[] = {
1763 { "quot", 34 },
1764 { "amp", 38 },
1765 { "lt", 60 },
1766 { "gt", 62 },
1767 { "nbsp", 160 },
1768 { "iexcl", 161 },
1769 { "cent", 162 },
1770 { "pound", 163 },
1771 { "curren", 164 },
1772 { "yen", 165 },
1773 { "brvbar", 166 },
1774 { "sect", 167 },
1775 { "uml", 168 },
1776 { "copy", 169 },
1777 { "ordf", 170 },
1778 { "laquo", 171 },
1779 { "not", 172 },
1780 { "shy", 173 },
1781 { "reg", 174 },
1782 { "macr", 175 },
1783 { "deg", 176 },
1784 { "plusmn", 177 },
1785 { "sup2", 178 },
1786 { "sup3", 179 },
1787 { "acute", 180 },
1788 { "micro", 181 },
1789 { "para", 182 },
1790 { "middot", 183 },
1791 { "cedil", 184 },
1792 { "sup1", 185 },
1793 { "ordm", 186 },
1794 { "raquo", 187 },
1795 { "frac14", 188 },
1796 { "frac12", 189 },
1797 { "frac34", 190 },
1798 { "iquest", 191 },
1799 { "Agrave", 192 },
1800 { "Aacute", 193 },
1801 { "Acirc", 194 },
1802 { "Atilde", 195 },
1803 { "Auml", 196 },
1804 { "Aring", 197 },
1805 { "AElig", 198 },
1806 { "Ccedil", 199 },
1807 { "Egrave", 200 },
1808 { "Eacute", 201 },
1809 { "Ecirc", 202 },
1810 { "Euml", 203 },
1811 { "Igrave", 204 },
1812 { "Iacute", 205 },
1813 { "Icirc", 206 },
1814 { "Iuml", 207 },
1815 { "ETH", 208 },
1816 { "Ntilde", 209 },
1817 { "Ograve", 210 },
1818 { "Oacute", 211 },
1819 { "Ocirc", 212 },
1820 { "Otilde", 213 },
1821 { "Ouml", 214 },
1822 { "times", 215 },
1823 { "Oslash", 216 },
1824 { "Ugrave", 217 },
1825 { "Uacute", 218 },
1826 { "Ucirc", 219 },
1827 { "Uuml", 220 },
1828 { "Yacute", 221 },
1829 { "THORN", 222 },
1830 { "szlig", 223 },
1831 { "agrave", 224 },
1832 { "aacute", 225 },
1833 { "acirc", 226 },
1834 { "atilde", 227 },
1835 { "auml", 228 },
1836 { "aring", 229 },
1837 { "aelig", 230 },
1838 { "ccedil", 231 },
1839 { "egrave", 232 },
1840 { "eacute", 233 },
1841 { "ecirc", 234 },
1842 { "euml", 235 },
1843 { "igrave", 236 },
1844 { "iacute", 237 },
1845 { "icirc", 238 },
1846 { "iuml", 239 },
1847 { "eth", 240 },
1848 { "ntilde", 241 },
1849 { "ograve", 242 },
1850 { "oacute", 243 },
1851 { "ocirc", 244 },
1852 { "otilde", 245 },
1853 { "ouml", 246 },
1854 { "divide", 247 },
1855 { "oslash", 248 },
1856 { "ugrave", 249 },
1857 { "uacute", 250 },
1858 { "ucirc", 251 },
1859 { "uuml", 252 },
1860 { "yacute", 253 },
1861 { "thorn", 254 },
1862 { "yuml", 255 },
1863 { "Alpha", 913 },
1864 { "alpha", 945 },
1865 { "Beta", 914 },
1866 { "beta", 946 },
1867 { "Gamma", 915 },
1868 { "gamma", 947 },
1869 { "Delta", 916 },
1870 { "delta", 948 },
1871 { "Epsilon", 917 },
1872 { "epsilon", 949 },
1873 { "Zeta", 918 },
1874 { "zeta", 950 },
1875 { "Eta", 919 },
1876 { "eta", 951 },
1877 { "Theta", 920 },
1878 { "theta", 952 },
1879 { "Iota", 921 },
1880 { "iota", 953 },
1881 { "Kappa", 922 },
1882 { "kappa", 954 },
1883 { "Lambda", 923 },
1884 { "lambda", 955 },
1885 { "Mu", 924 },
1886 { "mu", 956 },
1887 { "Nu", 925 },
1888 { "nu", 957 },
1889 { "Xi", 926 },
1890 { "xi", 958 },
1891 { "Omicron", 927 },
1892 { "omicron", 959 },
1893 { "Pi", 928 },
1894 { "pi", 960 },
1895 { "Rho", 929 },
1896 { "rho", 961 },
1897 { "Sigma", 931 },
1898 { "sigmaf", 962 },
1899 { "sigma", 963 },
1900 { "Tau", 932 },
1901 { "tau", 964 },
1902 { "Upsilon", 933 },
1903 { "upsilon", 965 },
1904 { "Phi", 934 },
1905 { "phi", 966 },
1906 { "Chi", 935 },
1907 { "chi", 967 },
1908 { "Psi", 936 },
1909 { "psi", 968 },
1910 { "Omega", 937 },
1911 { "omega", 969 },
1912 { "thetasym", 977 },
1913 { "upsih", 978 },
1914 { "piv", 982 },
1915 { "forall", 8704 },
1916 { "part", 8706 },
1917 { "exist", 8707 },
1918 { "empty", 8709 },
1919 { "nabla", 8711 },
1920 { "isin", 8712 },
1921 { "notin", 8713 },
1922 { "ni", 8715 },
1923 { "prod", 8719 },
1924 { "sum", 8721 },
1925 { "minus", 8722 },
1926 { "lowast", 8727 },
1927 { "radic", 8730 },
1928 { "prop", 8733 },
1929 { "infin", 8734 },
1930 { "ang", 8736 },
1931 { "and", 8869 },
1932 { "or", 8870 },
1933 { "cap", 8745 },
1934 { "cup", 8746 },
1935 { "int", 8747 },
1936 { "there4", 8756 },
1937 { "sim", 8764 },
1938 { "cong", 8773 },
1939 { "asymp", 8776 },
1940 { "ne", 8800 },
1941 { "equiv", 8801 },
1942 { "le", 8804 },
1943 { "ge", 8805 },
1944 { "sub", 8834 },
1945 { "sup", 8835 },
1946 { "nsub", 8836 },
1947 { "sube", 8838 },
1948 { "supe", 8839 },
1949 { "oplus", 8853 },
1950 { "otimes", 8855 },
1951 { "perp", 8869 },
1952 { "sdot", 8901 },
1953 { "loz", 9674 },
1954 { "lceil", 8968 },
1955 { "rceil", 8969 },
1956 { "lfloor", 8970 },
1957 { "rfloor", 8971 },
1958 { "lang", 9001 },
1959 { "rang", 9002 },
1960 { "larr", 8592 },
1961 { "uarr", 8593 },
1962 { "rarr", 8594 },
1963 { "darr", 8595 },
1964 { "harr", 8596 },
1965 { "crarr", 8629 },
1966 { "lArr", 8656 },
1967 { "uArr", 8657 },
1968 { "rArr", 8658 },
1969 { "dArr", 8659 },
1970 { "hArr", 8660 },
1971 { "bull", 8226 },
1972 { "hellip", 8230 },
1973 { "prime", 8242 },
1974 { "oline", 8254 },
1975 { "frasl", 8260 },
1976 { "weierp", 8472 },
1977 { "image", 8465 },
1978 { "real", 8476 },
1979 { "trade", 8482 },
1980 { "euro", 8364 },
1981 { "alefsym", 8501 },
1982 { "spades", 9824 },
1983 { "clubs", 9827 },
1984 { "hearts", 9829 },
1985 { "diams", 9830 },
1986 { "ensp", 8194 },
1987 { "emsp", 8195 },
1988 { "thinsp", 8201 },
1989 { "zwnj", 8204 },
1990 { "zwj", 8205 },
1991 { "lrm", 8206 },
1992 { "rlm", 8207 },
1993 { "ndash", 8211 },
1994 { "mdash", 8212 },
1995 { "lsquo", 8216 },
1996 { "rsquo", 8217 },
1997 { "sbquo", 8218 },
1998 { "ldquo", 8220 },
1999 { "rdquo", 8221 },
2000 { "bdquo", 8222 },
2001 { "dagger", 8224 },
2002 { "Dagger", 8225 },
2003 { "permil", 8240 },
2004 { "lsaquo", 8249 },
2005 { "rsaquo", 8250 },
2006 { NULL, 0 }
2008 gint i;
2010 g_return_val_if_fail( NULL != htmlname, 0 );
2012 if( '\0' == *htmlname )
2013 return 0;
2015 if( '#' == *htmlname ) {
2016 const gchar *iter = htmlname;
2017 gunichar c = 0;
2018 if( 0 > len )
2019 i = 7;
2020 else
2021 i = len - 1;
2022 iter++;
2023 while( isdigit( *iter ) && ( 0 <= i ) ) {
2024 c = c * 10 + (*iter - '0');
2025 iter++;
2026 i--;
2029 if( 0 >= len ) {
2030 if( '\0' == *iter )
2031 return c;
2033 else if( 0 == i )
2034 return c;
2036 return 0;
2040 if( 0 > len ) {
2041 for( i = 0; NULL != html2utf8_table[ i ].str; i++ )
2042 if( 0 == strcmp( htmlname, html2utf8_table[ i ].str ) )
2043 return html2utf8_table[ i ].utf8;
2045 else if( 0 < len ) {
2046 for( i = 0; NULL != html2utf8_table[ i ].str; i++ )
2047 if( 0 == strncmp( htmlname, html2utf8_table[ i ].str, len ) )
2048 return html2utf8_table[ i ].utf8;
2051 return 0xFFFC;
2055 * Convert a string containing HTML encoded unichars to valid UTF-8.
2057 static gchar *htmlstr2utf8( const gchar* str )
2059 const gchar *amp_pos, *colon_pos, *copy_pos;
2060 gunichar uni = 0;
2061 GString *result;
2062 gsize len;
2064 if( NULL == str )
2065 return NULL;
2067 result = g_string_new( NULL );
2068 colon_pos = str;
2069 copy_pos = colon_pos;
2071 do {
2072 amp_pos = strchr( colon_pos, '&' );
2073 if( NULL == amp_pos )
2074 break;
2075 colon_pos = amp_pos;
2077 len = 0;
2078 while( (';' != *colon_pos)
2079 && ('\0' != *colon_pos) )
2081 colon_pos++;
2082 len++;
2085 if( (9 > len) && (2 < len) ) {
2086 uni = htmlname2unichar( amp_pos + 1, colon_pos - amp_pos - 1 );
2087 if( (0 != uni) && (TRUE == g_unichar_validate( uni )) ) {
2088 g_string_append_len( result, copy_pos, amp_pos - copy_pos );
2089 colon_pos++;
2090 copy_pos = colon_pos;
2091 g_string_append_unichar( result, uni );
2094 amp_pos = colon_pos;
2096 while( NULL != amp_pos );
2098 if( NULL != copy_pos )
2099 g_string_append( result, copy_pos );
2101 return g_string_free( result, FALSE );
2104 static gchar * strip_tags(gchar *html)
2106 gsize i = 0,j=0;
2107 unsigned depth = 0;
2108 while(html[i] != '\0') {
2109 if(html[i] == '<') depth++;
2110 else if(html[i] == '>') depth--;
2111 else if(depth == 0) {
2112 html[j] = html[i];
2113 j++;
2115 i++;
2117 html[j] = '\0';
2118 return html;
2121 gchar * meta_data_get_text_from_html(const MetaData *data)
2123 gchar *retv;
2124 g_assert(meta_data_is_html(data));
2125 retv = htmlstr2utf8((gchar *)data->content);
2126 /* need to strip tags */
2127 retv = strip_tags(retv);
2128 return retv;
2130 gboolean meta_data_is_raw(const MetaData *data)
2132 return data->content_type == META_DATA_CONTENT_RAW;
2135 const guchar * meta_data_get_raw(const MetaData *data, gsize *length)
2137 g_assert(meta_data_is_raw(data));
2138 if(length)
2139 *length = data->size;
2140 return (guchar *)data->content;
2142 gboolean meta_data_is_text_vector(const MetaData *data)
2144 return data->content_type == META_DATA_CONTENT_TEXT_VECTOR;
2146 const gchar ** meta_data_get_text_vector(const MetaData *data)
2148 g_assert(meta_data_is_text_vector(data));
2149 return (const gchar **)data->content;
2152 gboolean meta_data_is_text_list(const MetaData *data)
2154 return data->content_type == META_DATA_CONTENT_TEXT_LIST;
2156 const GList *meta_data_get_text_list(const MetaData *data)
2158 g_assert(meta_data_is_text_list(data));
2159 return (const GList *)data->content;
2162 * Plugin structure
2165 gmpcPrefPlugin metadata_pref_plug = {
2166 .construct = metadata_construct_pref_pane,
2167 .destroy = metadata_destroy_pref_pane
2169 gmpcPlugin metadata_plug = {
2170 .name = N_("Metadata Handler"),
2171 .version = {1,1,1},
2172 .plugin_type = GMPC_INTERNALL,
2175 /* vim: set noexpandtab ts=4 sw=4 sts=4 tw=120: */