1 /* Gnome Music Player Client (GMPC)
2 * Copyright (C) 2004-2012 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 #define LOG_DOMAIN "MetaData"
31 #include <glyr/glyr.h>
32 #include <glyr/cache.h>
37 static GAsyncQueue
*gaq
= NULL
;
38 static GAsyncQueue
*return_queue
= NULL
;
39 static GlyrDatabase
*db
= NULL
;
43 * This queue is used to send replies back.
46 MTD_ACTION_QUERY_METADATA
,
47 MTD_ACTION_CLEAR_ENTRY
,
49 MTD_ACTION_QUERY_LIST
,
54 * Structure holding a metadata query */
56 /* The type of action todo */
57 enum MTD_Action action
;
58 /* unique id for the query (unused)*/
60 /* The callback to call when the query is done, or NULL */
61 MetaDataCallback callback
;
62 /* Callback user_data pointer*/
64 /* The song the data is queries for */
67 /* Original (for sending the right signal) */
70 /* The type of metadata */
73 MetaDataResult result
;
74 /* The actual result data */
80 // Validate if enough information is available for the query.
81 static gboolean
meta_data_validate_query(mpd_Song
*tsong
, MetaDataType type
)
83 switch(type
&META_QUERY_DATA_TYPES
)
85 case META_GENRE_SIMILAR
:
86 if(tsong
->genre
== NULL
|| tsong
->genre
[0] == '\0')
89 case META_SONG_GUITAR_TAB
:
91 case META_SONG_SIMILAR
:
92 if(tsong
->title
== NULL
|| tsong
->title
[0] == '\0')
94 if(tsong
->artist
== NULL
|| tsong
->artist
[0] == '\0')
99 if(tsong
->album
== NULL
|| tsong
->album
[0] == '\0')
101 case META_ARTIST_TXT
:
102 case META_ARTIST_SIMILAR
:
103 case META_BACKDROP_ART
:
104 case META_ARTIST_ART
:
105 if(tsong
->artist
== NULL
|| tsong
->artist
[0] == '\0')
109 case META_QUERY_DATA_TYPES
:
110 case META_QUERY_NO_CACHE
:
119 static void meta_thread_data_free(meta_thread_data
*mtd
)
121 /* Free the result data */
123 meta_data_free(mtd
->met
);
124 if(mtd
->met_results
) {
125 g_list_foreach(mtd
->met_results
, (GFunc
)meta_data_free
, NULL
);
126 g_list_free(mtd
->met_results
);
127 mtd
->met_results
= NULL
;
129 /* Free the copie and edited version of the songs */
131 mpd_freeSong(mtd
->song
);
134 mpd_freeSong(mtd
->ori_song
);
136 /* Free the Request struct */
137 g_slice_free(meta_thread_data
, mtd
);
139 gboolean
meta_compare_func(meta_thread_data
*mt1
, meta_thread_data
*mt2
);
140 //static gboolean meta_data_handle_results(void);
142 mpd_Song
*rewrite_mpd_song(mpd_Song
*tsong
, MetaDataType type
, gboolean query_mpd
)
144 mpd_Song
*edited
= NULL
;
145 /* If it is not a mpd got song */
146 if(tsong
->file
== NULL
&& query_mpd
)
148 if(type
&(META_ALBUM_ART
|META_ALBUM_TXT
) && tsong
->artist
&& tsong
->album
)
150 MpdData
*data2
= NULL
;
151 mpd_database_search_start(connection
, TRUE
);
152 mpd_database_search_add_constraint(connection
, MPD_TAG_ITEM_ARTIST
, tsong
->artist
);
153 mpd_database_search_add_constraint(connection
, MPD_TAG_ITEM_ALBUM
, tsong
->album
);
154 data2
= mpd_database_search_commit(connection
);
157 edited
= data2
->song
;
159 mpd_data_free(data2
);
161 else if(mpd_server_tag_supported(connection
,MPD_TAG_ITEM_ALBUM_ARTIST
) && tsong
->albumartist
)
163 mpd_database_search_start(connection
, TRUE
);
164 mpd_database_search_add_constraint(connection
, MPD_TAG_ITEM_ALBUM_ARTIST
, tsong
->albumartist
);
165 mpd_database_search_add_constraint(connection
, MPD_TAG_ITEM_ALBUM
, tsong
->album
);
166 data2
= mpd_database_search_commit(connection
);
169 edited
= data2
->song
;
171 mpd_data_free(data2
);
177 else if(type
&(META_ARTIST_ART
|META_ARTIST_TXT
) && tsong
->artist
)
179 MpdData
*data2
= NULL
;
180 mpd_database_search_start(connection
, TRUE
);
181 mpd_database_search_add_constraint(connection
, MPD_TAG_ITEM_ARTIST
, tsong
->artist
);
182 data2
= mpd_database_search_commit(connection
);
185 edited
= data2
->song
;
187 mpd_data_free(data2
);
192 edited
= mpd_songDup(tsong
);
195 * Collections detection
196 * Only do this for album related queries.
198 if(type
&(META_ALBUM_ART
|META_ALBUM_TXT
))
200 if(edited
->albumartist
)
203 g_free(edited
->artist
);
204 edited
->artist
= g_strdup(edited
->albumartist
);
207 else if(edited
->album
&& edited
->file
&& query_mpd
)
211 char *dir
= g_path_get_dirname(edited
->file
);
212 mpd_database_search_start(connection
,TRUE
);
213 mpd_database_search_add_constraint(connection
, MPD_TAG_ITEM_ALBUM
, edited
->album
);
216 data2
= mpd_database_search_commit(connection
);
219 for(i
=0;data2
; data2
= mpd_data_get_next(data2
))
221 if(strncasecmp(data2
->song
->file
, dir
, strlen(dir
))==0)
223 /* Check for NULL pointers */
224 if(data2
->song
->artist
&& edited
->artist
)
226 if(strcmp(data2
->song
->artist
, edited
->artist
))
235 g_free(edited
->artist
);
236 edited
->artist
= g_strdup("Various Artists");
242 * Artist renaming, Clapton, Eric -> Eric Clapton
243 if(edited->artist && cfg_get_single_value_as_int_with_default(config, "metadata", "rename", FALSE))
245 gchar **str = g_strsplit(edited->artist, ",", 2);
247 if(str[0] && str[1]) {
248 g_free(edited->artist);
249 edited->artist = g_strdup_printf("%s %s", g_strstrip(str[1]), g_strstrip(str[0]));
252 g_log(LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "string converted to: '%s'", edited->artist);
256 * Sanitize album name and so (remove () (
258 if(cfg_get_single_value_as_int_with_default(config
, "metadata", "sanitize", TRUE
))
264 char *album
= edited
->album
;
265 edited
->album
= g_malloc0((strlen(album
)+1)*sizeof(char));
266 length
= strlen(album
);
267 for(i
=0;i
< length
;i
++)
269 if(album
[i
] == '(') depth
++;
270 else if (album
[i
] == ')')depth
--;
271 else if (depth
== 0) {
272 edited
->album
[j
] = album
[i
];
277 /* Remove trailing and leading spaces */
278 edited
->album
= g_strstrip(edited
->album
);
283 char *title
= edited
->title
;
285 edited
->title
= g_malloc0((strlen(title
)+1)*sizeof(char));
286 length
= strlen(title
);
287 for(i
=0;i
< length
;i
++)
289 if(title
[i
] == '(') depth
++;
290 else if (title
[i
] == ')')depth
--;
291 else if (depth
== 0) {
292 edited
->title
[j
] = title
[i
];
297 /* Remove trailing and leading spaces */
298 edited
->title
= g_strstrip(edited
->title
);
306 * If the metadata thread managed to handle a result this function
307 * Will call the callback (from the main (gtk) thread)
309 static gboolean
glyr_return_queue(void *user_data
)
312 meta_thread_data
*mtd
= (meta_thread_data
*)g_async_queue_try_pop(return_queue
);
315 printf("Process results: %i\n", mtd
->action
);
316 if(mtd
->action
== MTD_ACTION_QUERY_METADATA
)
318 gmpc_meta_watcher_data_changed(gmw
,mtd
->ori_song
, (mtd
->type
)&META_QUERY_DATA_TYPES
, mtd
->result
,mtd
->met
);
321 mtd
->callback(mtd
->ori_song
, mtd
->result
, mtd
->met
, mtd
->data
);
323 } else if (mtd
->action
== MTD_ACTION_QUERY_LIST
)
326 MetaDataListCallback cb
= (MetaDataListCallback
)mtd
->callback
;
327 // TODO: Update callback for current usecase.
328 cb(NULL
, "", mtd
->met_results
, mtd
->data
);
330 cb(NULL
, "", NULL
, mtd
->data
);
332 }else if (mtd
->action
== MTD_ACTION_CLEAR_ENTRY
) {
333 printf("Signal no longer available.\n");
334 // Signal that this item is now no longer available.
335 gmpc_meta_watcher_data_changed(gmw
, mtd
->ori_song
, (mtd
->type
)&META_QUERY_DATA_TYPES
, META_DATA_UNAVAILABLE
, NULL
);
338 meta_thread_data_free(mtd
);
344 static MetaDataContentType
setup_glyr_query(GlyrQuery
*query
,
345 const meta_thread_data
*mtd
)
347 MetaDataContentType content_type
= META_DATA_CONTENT_RAW
;
348 MetaDataType type
= mtd
->type
&META_QUERY_DATA_TYPES
;
351 glyr_opt_force_utf8(query
, TRUE
);
353 glyr_opt_parallel(query
, 1);
354 glyr_opt_number(query
, 1);
355 // timeout 5 seconds.
356 glyr_opt_timeout(query
, 5);
358 glyr_opt_artist(query
,(char*)mtd
->song
->artist
);
359 glyr_opt_album (query
,(char*)mtd
->song
->album
);
360 glyr_opt_title (query
,(char*)mtd
->song
->title
);
362 /* set default type */
363 glyr_opt_type(query
, GLYR_GET_UNSURE
);
365 if(type
== META_ARTIST_ART
)
367 glyr_opt_type(query
, GLYR_GET_ARTIST_PHOTOS
);
368 content_type
= META_DATA_CONTENT_RAW
;
370 else if (type
== META_BACKDROP_ART
)
372 glyr_opt_type(query
, GLYR_GET_BACKDROPS
);
373 content_type
= META_DATA_CONTENT_RAW
;
375 else if(type
== META_ARTIST_TXT
)
377 glyr_opt_lang_aware_only(query
,TRUE
);
378 glyr_opt_type(query
, GLYR_GET_ARTISTBIO
);
379 content_type
= META_DATA_CONTENT_TEXT
;
381 else if(type
== META_ARTIST_SIMILAR
)
383 glyr_opt_type(query
, GLYR_GET_SIMILIAR_ARTISTS
);
384 // cfg_* is no longer thread safe
385 glyr_opt_number(query
,20);
386 content_type
= META_DATA_CONTENT_TEXT
;
388 else if(type
== META_ALBUM_ART
&&
389 mtd
->song
->album
!= NULL
)
391 glyr_opt_type(query
, GLYR_GET_COVERART
);
392 content_type
= META_DATA_CONTENT_RAW
;
394 else if(type
== META_ALBUM_TXT
&&
395 mtd
->song
->album
!= NULL
)
397 glyr_opt_type(query
, GLYR_GET_ALBUM_REVIEW
);
398 content_type
= META_DATA_CONTENT_TEXT
;
400 else if(type
== META_SONG_TXT
&&
401 mtd
->song
->title
!= NULL
)
403 glyr_opt_type(query
, GLYR_GET_LYRICS
);
404 content_type
= META_DATA_CONTENT_TEXT
;
406 else if(type
== META_SONG_SIMILAR
&&
407 mtd
->song
->title
!= NULL
)
409 glyr_opt_type(query
, GLYR_GET_SIMILIAR_SONGS
);
410 // cfg_* is no longer thread safe
411 glyr_opt_number(query
,20);
413 else if (type
== META_SONG_GUITAR_TAB
&&
416 glyr_opt_type(query
, GLYR_GET_GUITARTABS
);
417 content_type
= META_DATA_CONTENT_TEXT
;
420 g_warning("Unsupported metadata type, or insufficient info");
425 * Convert cache to MetaData object.
427 static MetaData
* glyr_get_similiar_song_names(GlyrMemCache
* cache
)
429 MetaData
* mtd
= NULL
;
432 if(cache
->data
!= NULL
)
434 gchar
** split
= g_strsplit(cache
->data
,"\n",0);
435 if(split
!= NULL
&& split
[0] != NULL
)
439 mtd
= meta_data_new();
440 mtd
->type
= META_SONG_SIMILAR
;
441 mtd
->plugin_name
= g_strdup(cache
->prov
);
442 mtd
->content_type
= META_DATA_CONTENT_TEXT_LIST
;
446 buffer
= g_strdup_printf("%s::%s",split
[1],split
[0]);
447 g_log(LOG_DOMAIN
,G_LOG_LEVEL_DEBUG
, "%s\n", buffer
);
450 mtd
->content
= g_list_append((GList
*) mtd
->content
, buffer
);
460 * Convert cache to MetaData object.
462 static MetaData
* glyr_get_similiar_artist_names(GlyrMemCache
* cache
)
464 MetaData
* mtd
= NULL
;
467 if(cache
->data
!= NULL
)
469 gchar
** split
= g_strsplit(cache
->data
,"\n",0);
473 mtd
= meta_data_new();
474 mtd
->type
= META_ARTIST_SIMILAR
;
475 mtd
->plugin_name
= g_strdup(cache
->prov
);
476 mtd
->content_type
= META_DATA_CONTENT_TEXT_LIST
;
480 mtd
->content
= g_list_append((GList
*) mtd
->content
,
481 g_strdup((char *)split
[0]));
490 * Convert GLYR result into gmpc result
492 static gboolean
process_glyr_result(GlyrMemCache
*cache
,
493 MetaDataContentType content_type
,
494 meta_thread_data
*mtd
)
496 gboolean retv
= FALSE
;
497 // If more then one time called and we allready have a results
498 // Push it in the results list.
499 if(mtd
->met
!= NULL
) {
500 mtd
->met_results
= g_list_prepend(mtd
->met_results
, (void*)mtd
->met
);
502 mtd
->result
= META_DATA_UNAVAILABLE
;
504 if(cache
== NULL
) return retv
;
505 if(cache
->rating
>= 0)
507 if(mtd
->type
== META_ARTIST_SIMILAR
)
509 MetaData
* cont
= glyr_get_similiar_artist_names(cache
);
513 mtd
->result
= META_DATA_AVAILABLE
;
517 else if (mtd
->type
== META_SONG_SIMILAR
)
520 cont
= glyr_get_similiar_song_names(cache
);
524 mtd
->result
= META_DATA_AVAILABLE
;
530 (mtd
->met
) = meta_data_new();
531 (mtd
->met
)->type
= mtd
->type
;
534 (mtd
->met
)->plugin_name
= g_strdup_printf("%s (cached)", cache
->prov
);
536 (mtd
->met
)->plugin_name
= g_strdup(cache
->prov
);
538 (mtd
->met
)->content_type
= content_type
;
541 mtd
->met
->content
= cache
->data
;
543 (mtd
->met
)->size
= cache
->size
;
545 mtd
->result
= META_DATA_AVAILABLE
;
549 memcpy(&(mtd
->met
->md5sum
), &(cache
->md5sum
), 16);
551 // Explicitely not found.
552 printf("Cache sais empty\n");
558 static GlyrQuery
*glyr_exit_handle
= NULL
;
559 static GStaticMutex exit_handle_lock
= G_STATIC_MUTEX_INIT
;
562 * Load a file from an URI
564 * @param mtd A meta_thread_data.
566 * Loads a file from a hard-drive.
570 static GlyrMemCache
*glyr_fetcher_thread_load_uri(meta_thread_data
*mtd
)
572 GlyrMemCache
*cache
= NULL
;
573 const char *path
= meta_data_get_uri(mtd
->met
);
574 gchar
*scheme
= g_uri_parse_scheme(path
);
576 if(scheme
== NULL
|| strcmp(scheme
, "file") == 0)
578 char *content
= NULL
;
580 g_file_get_contents(path
, &content
,&length
, NULL
);
582 mtd
->met
->content_type
= META_DATA_CONTENT_RAW
;
583 cache
= glyr_cache_new();
584 cache
->dsrc
= g_strdup(path
);
585 glyr_cache_set_data(cache
, content
, length
);
588 // Clean old content.
589 g_free(mtd
->met
->content
);
590 mtd
->met
->content
= g_memdup(content
, length
);
591 mtd
->met
->size
= length
;
595 // Testing force image type.
596 if(mtd
->met
->type
== META_ALBUM_ART
|| mtd
->met
->type
== META_ARTIST_ART
||
597 mtd
->met
->type
== META_BACKDROP_ART
)
599 cache
->is_image
= TRUE
;
600 cache
->img_format
= g_strdup("jpeg");
602 memcpy(&(mtd
->met
->md5sum
), &(cache
->md5sum
), 16);
609 static GlyrMemCache
*glyr_fetcher_thread_load_raw(meta_thread_data
*mtd
)
611 GlyrMemCache
*cache
= NULL
;
612 cache
= glyr_cache_new();
613 glyr_cache_set_data(cache
,
614 g_memdup(mtd
->met
->content
, mtd
->met
->size
),
616 // Testing force image type.
617 if(mtd
->met
->type
== META_ALBUM_ART
|| mtd
->met
->type
== META_ARTIST_ART
||
618 mtd
->met
->type
== META_BACKDROP_ART
)
620 cache
->is_image
= TRUE
;
621 cache
->img_format
= g_strdup("jpeg");
623 memcpy(mtd
->met
->md5sum
, cache
->md5sum
, 16);
627 static GlyrMemCache
*glyr_fetcher_thread_load_text(meta_thread_data
*mtd
)
629 GlyrMemCache
*cache
= NULL
;
630 cache
= glyr_cache_new();
631 glyr_cache_set_data(cache
,
632 g_strdup(mtd
->met
->content
),
634 memcpy(mtd
->met
->md5sum
, cache
->md5sum
, 16);
638 * Thread that does the GLYR requests
640 static void glyr_fetcher_thread(void *user_data
)
645 g_static_mutex_lock(&exit_handle_lock
);
646 while((d
= g_async_queue_pop(gaq
)))
648 meta_thread_data
*mtd
= (meta_thread_data
*)d
;
650 // Check if this is the quit command.
651 if(mtd
->action
== MTD_ACTION_QUIT
) {
652 printf("Quitting....");
653 g_static_mutex_unlock(&exit_handle_lock
);
654 /* Free the Request struct */
655 meta_thread_data_free(mtd
);
658 else if (mtd
->action
== MTD_ACTION_CLEAR_ENTRY
)
660 GlyrMemCache
*cache
= NULL
;
663 glyr_exit_handle
= &query
;
664 g_static_mutex_unlock(&exit_handle_lock
);
666 /* Set up the query */
667 glyr_query_init(&query
);
668 setup_glyr_query(&query
, mtd
);
669 glyr_opt_number(&query
, 0);
670 /* Set some random settings */
671 glyr_opt_verbosity(&query
,4);
673 // Delete existing entries.
674 glyr_db_delete(db
, &query
);
676 // Set dummy entry in cache, so we know
677 // we searched for this before.
678 cache
= glyr_cache_new();
679 // TODO: Remove this randomize hack.
680 glyr_cache_set_data(cache
,
681 g_strdup("GMPC Dummy"),
686 printf("Inserting dummy item\n");
687 glyr_db_insert(db
,&query
, cache
);
690 if(cache
)glyr_free_list(cache
);
692 // Clear the query, and lock the handle again.
693 g_static_mutex_lock(&exit_handle_lock
);
694 glyr_exit_handle
= NULL
;
695 glyr_query_destroy(&query
);
697 printf("Push back result\n");
698 // Push back result, and tell idle handle to handle it.
699 g_async_queue_push(return_queue
, mtd
);
700 // invalidate pointer.
702 // Schedule the result thread in idle time.
703 g_idle_add(glyr_return_queue
, NULL
);
705 else if (mtd
->action
== MTD_ACTION_QUERY_LIST
)
707 // Check if this is thread safe.
708 const char *md
= connection_get_music_directory();
709 GLYR_ERROR err
= GLYRE_OK
;
710 MetaDataContentType content_type
= META_DATA_CONTENT_RAW
;
711 GlyrMemCache
*cache
= NULL
;
713 glyr_exit_handle
= &query
;
714 g_static_mutex_unlock(&exit_handle_lock
);
716 printf("new style query\n");
718 glyr_query_init(&query
);
721 if(md
!= NULL
&& md
[0] != '\0'&& mtd
->song
->file
!= NULL
)
723 char *path
= g_build_filename(md
, mtd
->song
->file
, NULL
);
724 printf("music directory: \"%s\"\n", path
);
725 glyr_opt_musictree_path(&query
, path
);
730 /* Set up the query */
731 content_type
= setup_glyr_query(&query
, mtd
);
733 /* Set some random settings */
734 glyr_opt_verbosity(&query
,3);
736 /* Tell libglyr to automatically lookup before searching the web */
737 glyr_opt_lookup_db(&query
, db
);
739 /* Also tell it not to write newly found items to the db */
740 glyr_opt_db_autowrite(&query
, FALSE
);
741 /* We want many results */
742 glyr_opt_number(&query
, 25);
745 cache
= glyr_get(&query
,&err
,NULL
);
749 GlyrMemCache
*iter
= cache
;
751 process_glyr_result(iter
,content_type
, mtd
);
754 // Done. (most last item into list)
755 process_glyr_result(NULL
,content_type
, mtd
);
758 if(cache
)glyr_free_list(cache
);
760 // Clear the query, and lock the handle again.
761 g_static_mutex_lock(&exit_handle_lock
);
762 glyr_exit_handle
= NULL
;
763 glyr_query_destroy(&query
);
765 // Push back result, and tell idle handle to handle it.
766 g_async_queue_push(return_queue
, mtd
);
767 // invalidate pointer.
769 // Schedule the result thread in idle time.
770 g_idle_add(glyr_return_queue
, NULL
);
773 else if (mtd
->action
== MTD_ACTION_QUERY_METADATA
)
775 // Check if this is thread safe.
776 const char *md
= connection_get_music_directory();
777 GLYR_ERROR err
= GLYRE_OK
;
778 MetaDataContentType content_type
= META_DATA_CONTENT_RAW
;
779 GlyrMemCache
*cache
= NULL
;
781 glyr_exit_handle
= &query
;
782 g_static_mutex_unlock(&exit_handle_lock
);
784 printf("new style query\n");
786 glyr_query_init(&query
);
789 if(md
!= NULL
&& md
[0] != '\0'&& mtd
->song
->file
!= NULL
)
791 char *path
= g_build_filename(md
, mtd
->song
->file
, NULL
);
792 printf("music directory: \"%s\"\n", path
);
793 glyr_opt_musictree_path(&query
, path
);
798 /* Set up the query */
799 content_type
= setup_glyr_query(&query
, mtd
);
801 /* If cache disabled, remove the entry in the db */
802 if(((mtd
->type
)&META_QUERY_NO_CACHE
) == META_QUERY_NO_CACHE
)
804 printf("Disable cache\n");
805 glyr_opt_from(&query
, "all;-local");
806 // Remove cache request.
807 mtd
->type
&=META_QUERY_DATA_TYPES
;
809 glyr_db_delete(db
, &query
);
812 /* Set some random settings */
813 glyr_opt_verbosity(&query
,3);
815 /* Tell libglyr to automatically lookup before searching the web */
816 glyr_opt_lookup_db(&query
, db
);
818 /* Also tell it to write newly found items to the db */
819 glyr_opt_db_autowrite(&query
, TRUE
);
823 cache
= glyr_get(&query
,&err
,NULL
);
826 // Set dummy entry in cache, so we know
827 // we searched for this before.
828 cache
= glyr_cache_new();
829 // TODO: Remove this randomize hack.
830 glyr_cache_set_data(cache
,
831 g_strdup("GMPC Dummy"),
835 glyr_db_insert(db
,&query
, cache
);
836 printf("Cache is Empty\n");
838 mtd
->result
= META_DATA_UNAVAILABLE
;
840 process_glyr_result(cache
,content_type
, mtd
);
843 if(cache
)glyr_free_list(cache
);
845 // Clear the query, and lock the handle again.
846 g_static_mutex_lock(&exit_handle_lock
);
847 glyr_exit_handle
= NULL
;
848 glyr_query_destroy(&query
);
850 // Push back result, and tell idle handle to handle it.
851 g_async_queue_push(return_queue
, mtd
);
852 // invalidate pointer.
854 // Schedule the result thread in idle time.
855 g_idle_add(glyr_return_queue
, NULL
);
857 else if (mtd
->action
== MTD_ACTION_SET_ENTRY
)
859 GlyrMemCache
*cache
= NULL
;
862 glyr_exit_handle
= &query
;
863 g_static_mutex_unlock(&exit_handle_lock
);
865 /* Set up the query */
866 glyr_query_init(&query
);
867 setup_glyr_query(&query
, mtd
);
868 glyr_opt_number(&query
, 0);
869 /* Set some random settings */
870 glyr_opt_verbosity(&query
,3);
872 // Delete existing entries.
873 glyr_db_delete(db
, &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 if(meta_data_is_uri(mtd
->met
))
883 // try to load cache from file.
884 cache
= glyr_fetcher_thread_load_uri(mtd
);
886 else if (meta_data_is_raw(mtd
->met
))
888 // try to load cache from raw.
889 cache
= glyr_fetcher_thread_load_raw(mtd
);
890 } else if (meta_data_is_text(mtd
->met
) || meta_data_is_html(mtd
->met
))
892 // try to load cache from text.
893 cache
= glyr_fetcher_thread_load_text(mtd
);
900 printf("Do DB insert\n");
901 glyr_db_insert(db
,&query
, cache
);
904 // Clear the query, and lock the handle again.
905 g_static_mutex_lock(&exit_handle_lock
);
906 glyr_exit_handle
= NULL
;
907 glyr_query_destroy(&query
);
909 // Push back result, and tell idle handle to handle it.
910 // set it to query metadata to get the right handle behaviour.
911 mtd
->action
= MTD_ACTION_QUERY_METADATA
;
912 g_async_queue_push(return_queue
, mtd
);
913 // invalidate pointer.
917 // Schedule the result thread in idle time.
918 g_idle_add(glyr_return_queue
, NULL
);
920 g_error("Unknown type of query to perform");
929 GThread
*gaq_fetcher_thread
= NULL
;
930 void meta_data_init(void)
934 /* Is this function thread safe? */
935 url
= gmpc_get_covers_path("");
937 //g_mutex_init(&exit_handle_lock);
939 printf("open glyr db: %s\n", url
);
941 db
= glyr_db_init(url
);
945 gaq
= g_async_queue_new();
946 return_queue
= g_async_queue_new();
947 #if GLIB_CHECK_VERSION(2, 31, 0)
948 gaq_fetcher_thread
= g_thread_new("Glyr thread fetch", (GThreadFunc
)glyr_fetcher_thread
, NULL
);
950 gaq_fetcher_thread
= g_thread_create(glyr_fetcher_thread
, NULL
, TRUE
, NULL
);
956 * TODO: Can we guarantee that all the downloads are stopped?
958 void meta_data_destroy(void)
960 meta_thread_data
*mtd
= NULL
;
962 * Clear the request queue, and tell thread to quit
964 g_async_queue_lock(gaq
);
965 while((mtd
= g_async_queue_try_pop_unlocked(gaq
))){
967 meta_thread_data_free(mtd
);
969 mtd
= g_slice_new0(meta_thread_data
);//g_malloc0(sizeof(*mtd));
970 mtd
->action
= MTD_ACTION_QUIT
;
971 g_async_queue_push_unlocked(gaq
, mtd
);
973 g_async_queue_unlock(gaq
);
975 g_static_mutex_lock(&exit_handle_lock
);
976 if(glyr_exit_handle
) {
977 printf("Sending quit signal\n");
978 glyr_signal_exit(glyr_exit_handle
);
980 g_static_mutex_unlock(&exit_handle_lock
);
982 printf("Waiting for glyr to finish.....\n");
983 g_thread_join(gaq_fetcher_thread
);
984 //g_mutex_clear(&exit_handle_lock);
989 * Wait for thread to quit
991 g_async_queue_lock(return_queue
);
992 while((mtd
= g_async_queue_try_pop_unlocked(return_queue
))){
993 /* Free any possible plugin results */
994 meta_thread_data_free(mtd
);
996 g_async_queue_unlock(return_queue
);
997 g_async_queue_unref(gaq
);
998 g_async_queue_unref(return_queue
);
1001 gboolean
meta_compare_func(meta_thread_data
*mt1
, meta_thread_data
*mt2
)
1003 if(mt1
->action
!= mt2
->action
) return TRUE
;
1005 if((mt1
->type
&META_QUERY_DATA_TYPES
) != (mt2
->type
&META_QUERY_DATA_TYPES
))
1007 if(!gmpc_meta_watcher_match_data(mt1
->type
&META_QUERY_DATA_TYPES
, mt1
->song
, mt2
->song
))
1015 static guint meta_data_thread_data_uid
= 0;
1017 void meta_data_set_entry ( mpd_Song
*song
, MetaData
*met
)
1019 meta_thread_data
*mtd
= NULL
;
1020 if(song
== NULL
|| met
== NULL
|| !meta_data_validate_query(song
, met
->type
))
1022 g_warning("Trying to set metadata entry with insufficient information");
1026 mtd
= g_slice_new0(meta_thread_data
);
1027 mtd
->action
= MTD_ACTION_SET_ENTRY
;
1028 mtd
->id
= ++meta_data_thread_data_uid
;
1029 /* Create a copy of the original song */
1030 mtd
->song
= rewrite_mpd_song(song
, met
->type
, TRUE
);
1031 mtd
->ori_song
= mpd_songDup(song
);
1033 mtd
->type
= met
->type
;
1034 /* set result NULL */
1035 mtd
->met
= meta_data_dup(met
);;
1036 /* signal we are fetching. */
1037 gmpc_meta_watcher_data_changed(gmw
,mtd
->ori_song
, (mtd
->type
)&META_QUERY_DATA_TYPES
, META_DATA_FETCHING
,NULL
);
1039 printf("Request setting entry\n");
1040 g_async_queue_push(gaq
, mtd
);
1044 void meta_data_clear_entry(mpd_Song
*song
, MetaDataType type
)
1046 meta_thread_data
*mtd
= NULL
;
1047 if(!meta_data_validate_query(song
, type
))
1049 g_warning("Trying to clear metadata entry with insufficient information");
1052 mtd
= g_slice_new0(meta_thread_data
);
1053 mtd
->action
= MTD_ACTION_CLEAR_ENTRY
;
1054 mtd
->id
= ++meta_data_thread_data_uid
;
1055 /* Create a copy of the original song */
1056 mtd
->song
= rewrite_mpd_song(song
, type
,TRUE
);
1057 mtd
->ori_song
= mpd_songDup(song
);
1060 /* set result NULL */
1062 printf("Request clearing entry\n");
1063 g_async_queue_push(gaq
, mtd
);
1068 * Function called by the "client"
1071 MetaDataResult
meta_data_get_path(mpd_Song
*tsong
, MetaDataType type
, MetaData
**met
,MetaDataCallback callback
, gpointer data
)
1073 meta_thread_data
*mtd
= NULL
;
1075 if(!meta_data_validate_query(tsong
, type
))
1077 printf("Query invalid");
1079 return META_DATA_UNAVAILABLE
;
1082 mtd
= g_slice_new0(meta_thread_data
);
1083 mtd
->action
= MTD_ACTION_QUERY_METADATA
;
1084 mtd
->id
= ++meta_data_thread_data_uid
;
1085 /* Create a copy of the original song */
1086 mtd
->song
= rewrite_mpd_song(tsong
, type
, FALSE
);
1087 mtd
->ori_song
= mpd_songDup(tsong
);
1091 mtd
->callback
= callback
;
1092 /* the callback data */
1094 /* Set that we are fetching */
1095 mtd
->result
= META_DATA_FETCHING
;
1096 /* set result NULL */
1099 * If requested query the cache first
1101 if((type
&META_QUERY_NO_CACHE
) == 0)
1104 MetaDataContentType content_type
= META_DATA_CONTENT_RAW
;
1106 GlyrMemCache
* cache
= NULL
;
1108 glyr_query_init(&query
);
1109 /* Set some random settings */
1110 glyr_opt_verbosity(&query
,3);
1112 content_type
= setup_glyr_query(&query
, mtd
);
1113 cache
= glyr_db_lookup(db
, &query
);
1114 if(process_glyr_result(cache
,content_type
, mtd
))
1117 if(cache
)glyr_free_list(cache
);
1118 glyr_query_destroy(&query
);
1124 meta_thread_data_free(mtd
);
1128 if(cache
)glyr_free_list(cache
);
1129 glyr_query_destroy(&query
);
1133 printf("signal fetching\n");
1134 gmpc_meta_watcher_data_changed(gmw
,mtd
->ori_song
, (mtd
->type
)&META_QUERY_DATA_TYPES
, META_DATA_FETCHING
,NULL
);
1137 mtd
->callback(mtd
->ori_song
, META_DATA_FETCHING
, NULL
, mtd
->data
);
1141 // Rewrite for query
1142 if(mtd
->song
!= NULL
) mpd_freeSong(mtd
->song
);
1143 mtd
->song
= rewrite_mpd_song(tsong
, type
, TRUE
);
1146 g_async_queue_push(gaq
, mtd
);
1148 return META_DATA_FETCHING
;
1151 static gchar
* strip_invalid_chars(gchar
*input
)
1155 g_assert(input
!= NULL
);
1156 length
= strlen(input
);
1157 if(input
== NULL
) return NULL
;
1158 for(i
=0;i
<length
;i
++)
1180 * Helper function for storing metadata
1182 gchar
* gmpc_get_metadata_filename(MetaDataType type
, mpd_Song
*song
, char *ext
)
1186 const gchar
*homedir
= g_get_user_cache_dir();
1187 g_assert(song
->artist
!= NULL
);
1188 g_assert(type
< META_QUERY_DATA_TYPES
);
1191 GError
*error
= NULL
;
1192 gchar
*filename
= NULL
, *dirname
= NULL
;
1193 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
);
1195 /* Convert it so the filesystem likes it */
1196 /* TODO: Add error checking */
1198 dirname
= g_filename_from_utf8(song
->artist
, -1, NULL
, NULL
, NULL
);
1201 const gchar
*charset
;
1202 g_get_charset (&charset
);
1203 dirname
= g_convert_with_fallback (song
->artist
, -1,
1204 charset
, "UTF-8",(char *)"-", NULL
, NULL
, &error
);
1207 g_log(LOG_DOMAIN
, G_LOG_LEVEL_WARNING
, "Failed to convert %s to file encoding. '%s'", song
->artist
, error
->message
);
1208 g_error_free(error
);
1209 if(dirname
) g_free(dirname
);
1210 dirname
= g_strdup("invalid");
1212 dirname
= strip_invalid_chars(dirname
);
1213 retv
= g_build_path(G_DIR_SEPARATOR_S
, homedir
,"gmpc","metadata", dirname
,NULL
);
1214 if(g_file_test(retv
, G_FILE_TEST_EXISTS
) == FALSE
) {
1215 if(g_mkdir_with_parents(retv
, 0755) < 0) {
1216 g_error("Failed to create: %s\n", retv
);
1220 if(!g_file_test(retv
, G_FILE_TEST_IS_DIR
)) {
1221 g_error("File exists but is not a directory: %s\n", retv
);
1225 if(type
&(META_ALBUM_ART
|META_ALBUM_TXT
)) {
1227 g_assert(song
->album
!= NULL
);
1228 temp
=g_filename_from_utf8(song
->album
,-1,NULL
,NULL
,NULL
);
1229 filename
= g_strdup_printf("%s.%s", temp
,extension
);
1231 }else if(type
&META_ARTIST_ART
){
1232 filename
= g_strdup_printf("artist_IMAGE.%s", extension
);
1233 }else if (type
&META_ARTIST_TXT
){
1234 filename
= g_strdup_printf("artist_BIOGRAPHY.%s", extension
);
1235 }else if (type
&META_SONG_TXT
) {
1237 g_assert(song
->title
!= NULL
);
1238 temp
=g_filename_from_utf8(song
->title
,-1,NULL
,NULL
,NULL
);
1239 filename
= g_strdup_printf("%s_LYRIC.%s", temp
,extension
);
1241 }else if (type
&META_SONG_GUITAR_TAB
) {
1243 g_assert(song
->title
!= NULL
);
1244 temp
=g_filename_from_utf8(song
->title
,-1,NULL
,NULL
,NULL
);
1245 filename
= g_strdup_printf("%s_GUITAR_TAB.%s", temp
,extension
);
1248 filename
= strip_invalid_chars(filename
);
1249 retv
= g_build_path(G_DIR_SEPARATOR_S
, homedir
,"gmpc", "metadata", dirname
,filename
,NULL
);
1250 if(filename
) g_free(filename
);
1251 if(dirname
) g_free(dirname
);
1259 void metadata_get_list_cancel(gpointer data
)
1263 gpointer
metadata_get_list(mpd_Song
*song
, MetaDataType type
, void (*callback
)(gpointer handle
,const gchar
*plugin_name
, GList
*list
, gpointer data
), gpointer data
)
1265 meta_thread_data
*mtd
= NULL
;
1267 mtd
= g_slice_new0(meta_thread_data
);
1268 mtd
->action
= MTD_ACTION_QUERY_LIST
;
1269 mtd
->id
= ++meta_data_thread_data_uid
;
1270 /* Create a copy of the original song */
1271 mtd
->song
= rewrite_mpd_song(song
, type
, TRUE
);
1272 mtd
->ori_song
= mpd_songDup(song
);
1276 mtd
->callback
= callback
;
1277 /* the callback data */
1279 /* Set that we are fetching */
1280 mtd
->result
= META_DATA_FETCHING
;
1281 /* set result NULL */
1283 mtd
->met_results
= NULL
;
1284 printf("start query\n");
1286 g_async_queue_push(gaq
, mtd
);
1293 MetaData
*meta_data_new(void)
1295 /* Create a new structure completely filled with 0's */
1296 MetaData
*retv
= g_new0(MetaData
, 1);
1297 retv
->content_type
= META_DATA_CONTENT_EMPTY
;
1301 void meta_data_free(MetaData
*data
)
1303 if(data
== NULL
) return;
1305 if(data
->content_type
== META_DATA_CONTENT_TEXT_VECTOR
)
1307 g_strfreev(data
->content
);
1309 else if (data
->content_type
== META_DATA_CONTENT_TEXT_LIST
)
1311 g_list_foreach((GList
*)data
->content
, (GFunc
)g_free
, NULL
);
1312 g_list_free((GList
*)data
->content
);
1314 else if(data
->content_type
!= META_DATA_CONTENT_EMPTY
)
1315 g_free(data
->content
);
1316 data
->content
= NULL
;
1319 if(data
->thumbnail_uri
) g_free(data
->thumbnail_uri
);
1320 data
->thumbnail_uri
= NULL
;
1321 if(data
->plugin_name
) g_free(data
->plugin_name
);
1326 MetaData
*meta_data_dup(MetaData
*data
)
1328 MetaData
*retv
= meta_data_new();
1329 g_assert(data
!= NULL
);
1330 /* Copy type of metadata */
1331 retv
->type
= data
->type
;
1332 /* Copy the type of the data */
1333 retv
->content_type
= data
->content_type
;
1334 /* Copy the name of the providing plugin. (const char * so only copy pointer ) */
1335 retv
->plugin_name
= g_strdup(data
->plugin_name
);
1336 /* copy the content */
1337 retv
->size
= data
->size
;
1338 if(retv
->content_type
== META_DATA_CONTENT_TEXT_VECTOR
) {
1339 if(data
->content
) retv
->content
=(void *) g_strdupv((gchar
**)data
->content
);
1341 /* raw data always needs a length */
1342 else if (data
->content_type
== META_DATA_CONTENT_RAW
)
1344 if(data
->size
> 0 ) {
1345 retv
->content
= g_memdup(data
->content
, (guint
)data
->size
);
1348 else if (data
->content_type
== META_DATA_CONTENT_TEXT_LIST
)
1351 GList
*iter
= g_list_first((GList
*)(data
->content
));
1352 while((iter
= g_list_next(iter
)))
1354 list
= g_list_append(list
, g_strdup(iter
->data
));
1356 data
->content
=(void *) g_list_reverse(list
);
1358 else if (data
->content_type
== META_DATA_CONTENT_EMPTY
)
1360 retv
->content
= NULL
; retv
->size
= 0;
1362 /* Text is NULL terminated */
1365 retv
->content
= NULL
;
1367 retv
->content
= g_strdup((gchar
*)data
->content
);
1370 if(data
->thumbnail_uri
!= NULL
) {
1371 retv
->thumbnail_uri
= g_strdup(data
->thumbnail_uri
);
1374 memcpy(&(retv
->md5sum
),&(data
->md5sum
), 16);
1378 MetaData
*meta_data_dup_steal(MetaData
*data
)
1380 MetaData
*retv
= meta_data_new();
1381 g_assert(data
!= NULL
);
1382 /* Copy type of metadata */
1383 retv
->type
= data
->type
;
1384 /* Copy the type of the data */
1385 retv
->content_type
= data
->content_type
;
1386 /* Copy the name of the providing plugin. (const char * so only copy pointer ) */
1387 retv
->plugin_name
= g_strdup(data
->plugin_name
);
1388 /* copy the content */
1389 retv
->size
= data
->size
;
1390 retv
->content
= data
->content
;
1392 data
->content
= NULL
;
1393 retv
->thumbnail_uri
= data
->thumbnail_uri
;
1394 data
->thumbnail_uri
= NULL
;
1396 memcpy(&(retv
->md5sum
),&(data
->md5sum
), 16);
1400 gboolean
meta_data_is_empty(const MetaData
*data
)
1402 return data
->content_type
== META_DATA_CONTENT_EMPTY
;
1404 gboolean
meta_data_is_uri(const MetaData
*data
)
1406 return data
->content_type
== META_DATA_CONTENT_URI
;
1408 const gchar
* meta_data_get_uri(const MetaData
*data
)
1410 g_assert(meta_data_is_uri(data
));
1411 return (const gchar
*)data
->content
;
1413 void meta_data_set_uri(MetaData
*data
, const gchar
*uri
)
1415 g_assert(meta_data_is_uri(data
));
1416 if(data
->content
) g_free(data
->content
);
1417 data
->content
= g_strdup(uri
);
1419 void meta_data_set_raw(MetaData
*item
, guchar
*data
, gsize len
)
1421 g_assert(meta_data_is_raw(item
));
1422 if(item
->content
) g_free(item
->content
);
1423 item
->content
= g_memdup(data
, len
);
1426 void meta_data_set_raw_owned(MetaData
*item
, guchar
**data
, gsize
*len
)
1428 g_assert(meta_data_is_raw(item
));
1429 if(item
->content
) g_free(item
->content
);
1430 item
->content
= *data
;
1435 void meta_data_set_thumbnail_uri(MetaData
*data
, const gchar
*uri
)
1437 g_assert(meta_data_is_uri(data
));
1438 if(data
->thumbnail_uri
) g_free(data
->thumbnail_uri
);
1439 data
->thumbnail_uri
= g_strdup(uri
);
1441 const gchar
* meta_data_get_thumbnail_uri(const MetaData
*data
)
1443 g_assert(meta_data_is_uri(data
));
1444 /* Only valid for images. */
1445 g_assert((data
->type
&(META_ALBUM_ART
|META_ARTIST_ART
)) != 0);
1447 return (const gchar
*)data
->thumbnail_uri
;
1450 gboolean
meta_data_is_text(const MetaData
*data
)
1452 return data
->content_type
== META_DATA_CONTENT_TEXT
;
1455 void meta_data_set_text(MetaData
*data
, const gchar
*text
)
1457 if(meta_data_is_text(data
))
1459 if(data
->content
) g_free(data
->content
);
1460 data
->content
= g_strdup(text
);
1464 const gchar
* meta_data_get_text(const MetaData
*data
)
1466 g_assert(meta_data_is_text(data
));
1467 return (const gchar
*)data
->content
;
1470 gboolean
meta_data_is_html(const MetaData
*data
)
1472 return data
->content_type
== META_DATA_CONTENT_HTML
;
1474 const gchar
* meta_data_get_html(const MetaData
*data
)
1476 g_assert(meta_data_is_html(data
));
1477 return (const gchar
*)data
->content
;
1482 * Convert a html encoded token (like & and 
1483 * to the corresponding unichar.
1485 * On early falire (i.e. empty string) returns 0.
1486 * If the encoding is wrong, it returns the unicode error value 0xFFFC.
1488 static gunichar
htmlname2unichar( const gchar
* htmlname
, gint len
)
1490 // * can be optimized by sorting
1491 // * and checking str(n)cmp results
1496 html2utf8_table
[] = {
1646 { "thetasym", 977 },
1715 { "alefsym", 8501 },
1744 g_return_val_if_fail( NULL
!= htmlname
, 0 );
1746 if( '\0' == *htmlname
)
1749 if( '#' == *htmlname
) {
1750 const gchar
*iter
= htmlname
;
1757 while( isdigit( *iter
) && ( 0 <= i
) ) {
1758 c
= c
* 10 + (*iter
- '0');
1775 for( i
= 0; NULL
!= html2utf8_table
[ i
].str
; i
++ )
1776 if( 0 == strcmp( htmlname
, html2utf8_table
[ i
].str
) )
1777 return html2utf8_table
[ i
].utf8
;
1779 else if( 0 < len
) {
1780 for( i
= 0; NULL
!= html2utf8_table
[ i
].str
; i
++ )
1781 if( 0 == strncmp( htmlname
, html2utf8_table
[ i
].str
, len
) )
1782 return html2utf8_table
[ i
].utf8
;
1789 * Convert a string containing HTML encoded unichars to valid UTF-8.
1791 static gchar
*htmlstr2utf8( const gchar
* str
)
1793 const gchar
*amp_pos
, *colon_pos
, *copy_pos
;
1801 result
= g_string_new( NULL
);
1803 copy_pos
= colon_pos
;
1806 amp_pos
= strchr( colon_pos
, '&' );
1807 if( NULL
== amp_pos
)
1809 colon_pos
= amp_pos
;
1812 while( (';' != *colon_pos
)
1813 && ('\0' != *colon_pos
) )
1819 if( (9 > len
) && (2 < len
) ) {
1820 uni
= htmlname2unichar( amp_pos
+ 1, colon_pos
- amp_pos
- 1 );
1821 if( (0 != uni
) && (TRUE
== g_unichar_validate( uni
)) ) {
1822 g_string_append_len( result
, copy_pos
, amp_pos
- copy_pos
);
1824 copy_pos
= colon_pos
;
1825 g_string_append_unichar( result
, uni
);
1828 amp_pos
= colon_pos
;
1830 while( NULL
!= amp_pos
);
1832 if( NULL
!= copy_pos
)
1833 g_string_append( result
, copy_pos
);
1835 return g_string_free( result
, FALSE
);
1838 static gchar
* strip_tags(gchar
*html
)
1842 while(html
[i
] != '\0') {
1843 if(html
[i
] == '<') depth
++;
1844 else if(html
[i
] == '>') depth
--;
1845 else if(depth
== 0) {
1855 gchar
* meta_data_get_text_from_html(const MetaData
*data
)
1858 g_assert(meta_data_is_html(data
));
1859 retv
= htmlstr2utf8((gchar
*)data
->content
);
1860 /* need to strip tags */
1861 retv
= strip_tags(retv
);
1864 gboolean
meta_data_is_raw(const MetaData
*data
)
1866 return data
->content_type
== META_DATA_CONTENT_RAW
;
1869 const guchar
* meta_data_get_raw(const MetaData
*data
, gsize
*length
)
1871 g_assert(meta_data_is_raw(data
));
1873 *length
= data
->size
;
1874 return (guchar
*)data
->content
;
1876 gboolean
meta_data_is_text_vector(const MetaData
*data
)
1878 return data
->content_type
== META_DATA_CONTENT_TEXT_VECTOR
;
1880 const gchar
** meta_data_get_text_vector(const MetaData
*data
)
1882 g_assert(meta_data_is_text_vector(data
));
1883 return (const gchar
**)data
->content
;
1886 gboolean
meta_data_is_text_list(const MetaData
*data
)
1888 return data
->content_type
== META_DATA_CONTENT_TEXT_LIST
;
1890 const GList
*meta_data_get_text_list(const MetaData
*data
)
1892 g_assert(meta_data_is_text_list(data
));
1893 return (const GList
*)data
->content
;
1899 gmpcPlugin metadata_plug
= {
1900 .name
= N_("Metadata Handler"),
1902 .plugin_type
= GMPC_INTERNALL
,
1905 /* vim: set noexpandtab ts=4 sw=4 sts=4 tw=120: */