1 /* Gnome Music Player Client (GMPC)
2 * Copyright (C) 2004-2011 Qball Cow <qball@gmpclient.org>
3 * Project homepage: http://gmpclient.org/
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include <glib/gstdio.h>
27 #include "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);
45 static GList
*process_queue
= NULL
;
46 static GAsyncQueue
*gaq
= NULL
;
47 static GAsyncQueue
*return_queue
= NULL
;
48 static GlyrDatabase
*db
= NULL
;
52 * This queue is used to send replies back.
55 MTD_ACTION_QUERY_METADATA
,
56 MTD_ACTION_CLEAR_ENTRY
,
58 MTD_ACTION_QUERY_LIST
,
63 * Structure holding a metadata query */
65 /* The type of action todo */
66 enum MTD_Action action
;
67 /* unique id for the query (unused)*/
69 /* The callback to call when the query is done, or NULL */
70 MetaDataCallback callback
;
71 /* Callback user_data pointer*/
73 /* The song the data is queries for */
75 /* The type of metadata */
78 MetaDataResult result
;
79 /* The actual result data */
85 // Validate if enough information is available for the query.
86 static gboolean
meta_data_validate_query(mpd_Song
*tsong
, MetaDataType type
)
90 case META_GENRE_SIMILAR
:
91 if(tsong
->genre
== NULL
|| tsong
->genre
[0] == '\0')
94 case META_SONG_GUITAR_TAB
:
96 case META_SONG_SIMILAR
:
97 if(tsong
->title
== NULL
|| tsong
->title
[0] == '\0')
99 if(tsong
->artist
== NULL
|| tsong
->artist
[0] == '\0')
104 if(tsong
->album
== NULL
|| tsong
->album
[0] == '\0')
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')
114 case META_QUERY_DATA_TYPES
:
115 case META_QUERY_NO_CACHE
:
124 static void meta_thread_data_free(meta_thread_data
*mtd
)
126 /* Free the result data */
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 */
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
);
159 edited
= data2
->song
;
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
);
171 edited
= data2
->song
;
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
);
187 edited
= data2
->song
;
189 mpd_data_free(data2
);
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
)
205 g_free(edited
->artist
);
206 edited
->artist
= g_strdup(edited
->albumartist
);
209 else if(edited
->album
&& edited
->file
)
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
);
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
))
237 g_free(edited
->artist
);
238 edited
->artist
= g_strdup("Various Artists");
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]));
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
))
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
];
279 /* Remove trailing and leading spaces */
280 edited
->album
= g_strstrip(edited
->album
);
285 char *title
= edited
->title
;
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
];
299 /* Remove trailing and leading spaces */
300 edited
->title
= g_strstrip(edited
->title
);
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
);
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
);
324 mtd
->callback(mtd
->song
, mtd
->result
, mtd
->met
, mtd
->data
);
326 } else if (mtd
->action
== MTD_ACTION_QUERY_LIST
)
329 MetaDataListCallback cb
= (MetaDataListCallback
)mtd
->callback
;
330 // TODO: Update callback for current usecase.
331 cb(NULL
, "", mtd
->met_results
, mtd
->data
);
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
);
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
;
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);
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
&&
419 glyr_opt_type(query
, GLYR_GET_GUITARTABS
);
420 content_type
= META_DATA_CONTENT_TEXT
;
423 g_warning("Unsupported metadata type, or insufficient info");
428 * Convert cache to MetaData object.
430 static MetaData
* glyr_get_similiar_song_names(GlyrMemCache
* cache
)
432 MetaData
* mtd
= NULL
;
435 if(cache
->data
!= NULL
)
437 gchar
** split
= g_strsplit(cache
->data
,"\n",0);
438 if(split
!= NULL
&& split
[0] != NULL
)
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
;
449 buffer
= g_strdup_printf("%s::%s",split
[1],split
[0]);
450 g_log(LOG_DOMAIN
,G_LOG_LEVEL_DEBUG
, "%s\n", buffer
);
453 mtd
->content
= g_list_append((GList
*) mtd
->content
, buffer
);
463 * Convert cache to MetaData object.
465 static MetaData
* glyr_get_similiar_artist_names(GlyrMemCache
* cache
)
467 MetaData
* mtd
= NULL
;
470 if(cache
->data
!= NULL
)
472 gchar
** split
= g_strsplit(cache
->data
,"\n",0);
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
;
483 mtd
->content
= g_list_append((GList
*) mtd
->content
,
484 g_strdup((char *)split
[0]));
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
;
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
);
516 mtd
->result
= META_DATA_AVAILABLE
;
520 else if (mtd
->type
== META_SONG_SIMILAR
)
523 cont
= glyr_get_similiar_song_names(cache
);
527 mtd
->result
= META_DATA_AVAILABLE
;
533 (mtd
->met
) = meta_data_new();
534 (mtd
->met
)->type
= mtd
->type
;
537 (mtd
->met
)->plugin_name
= g_strdup_printf("%s (cached)", cache
->prov
);
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);
548 mtd
->met
->content
= cache
->data
;
550 (mtd
->met
)->size
= cache
->size
;
552 mtd
->result
= META_DATA_AVAILABLE
;
556 memcpy(&(mtd
->met
->md5sum
), &(cache
->md5sum
), 16);
557 mtd
->met
->md5sum
[16] = '\0';
559 // Explicitely not found.
560 printf("Cache sais empty\n");
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.
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
;
588 g_file_get_contents(path
, &content
,&length
, NULL
);
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
;
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';
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
),
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';
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
),
644 memcpy(mtd
->met
->md5sum
, cache
->md5sum
, 16);
645 mtd
->met
->md5sum
[16] = '\0';
649 * Thread that does the GLYR requests
651 static void glyr_fetcher_thread(void *user_data
)
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
);
669 else if (mtd
->action
== MTD_ACTION_CLEAR_ENTRY
)
671 GlyrMemCache
*cache
= NULL
;
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"),
697 printf("Inserting dummy item\n");
698 glyr_db_insert(db
,&query
, cache
);
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.
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
);
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);
755 cache
= glyr_get(&query
,&err
,NULL
);
759 GlyrMemCache
*iter
= cache
;
761 process_glyr_result(iter
,content_type
, mtd
);
764 // Done. (most last item into list)
765 process_glyr_result(NULL
,content_type
, mtd
);
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.
779 // Schedule the result thread in idle time.
780 g_idle_add(glyr_return_queue
, NULL
);
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
);
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
;
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
);
832 cache
= glyr_get(&query
,&err
,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"),
844 glyr_db_insert(db
,&query
, cache
);
845 printf("Cache is Empty\n");
847 mtd
->result
= META_DATA_UNAVAILABLE
;
849 process_glyr_result(cache
,content_type
, mtd
);
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.
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
;
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);
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
);
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.
926 // Schedule the result thread in idle time.
927 g_idle_add(glyr_return_queue
, NULL
);
929 g_error("Unknown type of query to perform");
938 GThread
*gaq_fetcher_thread
= NULL
;
939 void meta_data_init(void)
943 /* Is this function thread safe? */
944 url
= gmpc_get_covers_path("");
946 //g_mutex_init(&exit_handle_lock);
948 printf("open glyr db: %s\n", url
);
950 db
= glyr_db_init(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
);
959 gaq_fetcher_thread
= g_thread_create(glyr_fetcher_thread
, NULL
, TRUE
, NULL
);
964 void meta_data_add_plugin(gmpcPluginParent
*plug
)
966 g_assert(plug
!= NULL
);
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)
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
];
987 meta_plugins
[i
] = meta_plugins
[i
+1];
988 meta_plugins
[i
+1] = temp
;
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
);
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
))){
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
);
1030 g_async_queue_unlock(gaq
);
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
);
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
))
1064 if(!gmpc_meta_watcher_match_data(mt1
->type
&META_QUERY_DATA_TYPES
, mt1
->song
, mt2
->song
))
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");
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
);
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
);
1095 printf("Request setting entry\n");
1096 g_async_queue_push(gaq
, mtd
);
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");
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
);
1116 /* set result NULL */
1118 printf("Request clearing entry\n");
1119 g_async_queue_push(gaq
, mtd
);
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
))
1134 return META_DATA_UNAVAILABLE
;
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
);
1151 mtd
->callback
= callback
;
1152 /* the callback data */
1154 /* Set that we are fetching */
1155 mtd
->result
= META_DATA_FETCHING
;
1156 /* set result 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
;
1166 MetaDataContentType content_type
= META_DATA_CONTENT_RAW
;
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
);
1181 content_type
= setup_glyr_query(&query
, mtd
);
1182 cache
= glyr_db_lookup(db
, &query
);
1183 if(process_glyr_result(cache
,content_type
, mtd
))
1186 if(cache
)glyr_free_list(cache
);
1187 glyr_query_destroy(&query
);
1193 meta_thread_data_free(mtd
);
1197 if(cache
)glyr_free_list(cache
);
1198 glyr_query_destroy(&query
);
1202 gmpc_meta_watcher_data_changed(gmw
,mtd
->song
, (mtd
->type
)&META_QUERY_DATA_TYPES
, META_DATA_FETCHING
,NULL
);
1205 mtd
->callback(mtd
->song
, META_DATA_FETCHING
, NULL
, mtd
->data
);
1208 printf("start query\n");
1211 g_async_queue_push(gaq
, mtd
);
1213 return META_DATA_FETCHING
;
1216 static gchar
* strip_invalid_chars(gchar
*input
)
1220 g_assert(input
!= NULL
);
1221 length
= strlen(input
);
1222 if(input
== NULL
) return NULL
;
1223 for(i
=0;i
<length
;i
++)
1245 * Helper function for storing metadata
1247 gchar
* gmpc_get_metadata_filename(MetaDataType type
, mpd_Song
*song
, char *ext
)
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);
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);
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);
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
);
1297 if(!g_file_test(retv
, G_FILE_TEST_IS_DIR
)) {
1298 g_error("File exists but is not a directory: %s\n", retv
);
1302 if(type
&(META_ALBUM_ART
|META_ALBUM_TXT
)) {
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
);
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
) {
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
);
1318 }else if (type
&META_SONG_GUITAR_TAB
) {
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
);
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
);
1332 static void metadata_pref_priority_changed(GtkCellRenderer
*renderer
, char *path
, char *new_text
, GtkListStore
*store
)
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);
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
,
1356 gmpcPluginParent
*plug
;
1357 gtk_tree_model_get(GTK_TREE_MODEL(model
), iter
, 0, &plug
, -1);
1360 gboolean active
= gmpc_plugin_get_enabled(plug
);
1361 g_object_set(G_OBJECT(cell
), "active", active
, NULL
);
1367 static void __column_toggled_enabled(GtkCellRendererToggle
*renderer
,
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);
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
;
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
);
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
);
1412 renderer
= gtk_cell_renderer_toggle_new();
1413 gtk_tree_view_insert_column_with_data_func(GTK_TREE_VIEW(treeview
),
1417 (GtkTreeCellDataFunc
)__column_data_func_enabled
,
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
),
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
),
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
++)
1454 gtk_list_store_insert_with_values(store
, &iter
, -1,
1456 1, gmpc_plugin_get_name(meta_plugins
[i
]),
1457 2, gmpc_plugin_metadata_get_priority(meta_plugins
[i
]),
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
));
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
;
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
);
1499 mtd
->callback
= callback
;
1500 /* the callback data */
1502 /* Set that we are fetching */
1503 mtd
->result
= META_DATA_FETCHING
;
1504 /* set result NULL */
1506 mtd
->met_results
= NULL
;
1507 printf("start query\n");
1509 TEC("Pushing actual query");
1511 g_async_queue_push(gaq
, mtd
);
1515 // callback(NULL, NULL, NULL, data);
1518 MLQuery
*q
= g_malloc0(sizeof(*q
));
1520 q
->callback
= callback
;
1525 * Create a copy, so song is guarantee to be valid during queries of plugins
1527 q
->song
= mpd_songDup(song
);
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
);
1542 g_log("MetaData", G_LOG_LEVEL_DEBUG
, "Cleanup done");
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
);
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
;
1565 void meta_data_free(MetaData
*data
)
1567 if(data
== NULL
) return;
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
;
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
);
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
)
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 */
1629 retv
->content
= NULL
;
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';
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
;
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';
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
);
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
;
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
);
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 & and 
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
1762 html2utf8_table
[] = {
1912 { "thetasym", 977 },
1981 { "alefsym", 8501 },
2010 g_return_val_if_fail( NULL
!= htmlname
, 0 );
2012 if( '\0' == *htmlname
)
2015 if( '#' == *htmlname
) {
2016 const gchar
*iter
= htmlname
;
2023 while( isdigit( *iter
) && ( 0 <= i
) ) {
2024 c
= c
* 10 + (*iter
- '0');
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
;
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
;
2067 result
= g_string_new( NULL
);
2069 copy_pos
= colon_pos
;
2072 amp_pos
= strchr( colon_pos
, '&' );
2073 if( NULL
== amp_pos
)
2075 colon_pos
= amp_pos
;
2078 while( (';' != *colon_pos
)
2079 && ('\0' != *colon_pos
) )
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
);
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
)
2108 while(html
[i
] != '\0') {
2109 if(html
[i
] == '<') depth
++;
2110 else if(html
[i
] == '>') depth
--;
2111 else if(depth
== 0) {
2121 gchar
* meta_data_get_text_from_html(const MetaData
*data
)
2124 g_assert(meta_data_is_html(data
));
2125 retv
= htmlstr2utf8((gchar
*)data
->content
);
2126 /* need to strip tags */
2127 retv
= strip_tags(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
));
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
;
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"),
2172 .plugin_type
= GMPC_INTERNALL
,
2175 /* vim: set noexpandtab ts=4 sw=4 sts=4 tw=120: */