Fix dead assignement.
[vlc.git] / modules / demux / playlist / itml.c
1 /*******************************************************************************
2  * itml.c : iTunes Music Library import functions
3  *******************************************************************************
4  * Copyright (C) 2007 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Yoann Peronneau <yoann@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *******************************************************************************/
23 /**
24  * \file modules/demux/playlist/itml.c
25  * \brief iTunes Music Library import functions
26  */
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <vlc_demux.h>
34 #include <vlc_xml.h>
35 #include <vlc_strings.h>
36 #include <vlc_url.h>
37
38 #include "itml.h"
39 #include "playlist.h"
40
41 struct demux_sys_t
42 {
43     int i_ntracks;
44 };
45
46 static int Control( demux_t *, int, va_list );
47 static int Demux( demux_t * );
48
49 /**
50  * \brief iTML submodule initialization function
51  */
52 int Import_iTML( vlc_object_t *p_this )
53 {
54     DEMUX_BY_EXTENSION_OR_FORCED_MSG( ".xml", "itml",
55                                       "using iTunes Media Library reader" );
56     return VLC_SUCCESS;
57 }
58
59 void Close_iTML( vlc_object_t *p_this )
60 {
61     demux_t *p_demux = (demux_t *)p_this;
62     free( p_demux->p_sys );
63 }
64
65 /**
66  * \brief demuxer function for iTML parsing
67  */
68 int Demux( demux_t *p_demux )
69 {
70     xml_t *p_xml;
71     xml_reader_t *p_xml_reader = NULL;
72     char *psz_name = NULL;
73
74     INIT_PLAYLIST_STUFF;
75     p_demux->p_sys->i_ntracks = 0;
76
77     /* create new xml parser from stream */
78     p_xml = xml_Create( p_demux );
79     if( !p_xml )
80         goto end;
81
82     p_xml_reader = xml_ReaderCreate( p_xml, p_demux->s );
83     if( !p_xml_reader )
84         goto end;
85
86     /* locating the root node */
87     do
88     {
89         if( xml_ReaderRead( p_xml_reader ) != 1 )
90         {
91             msg_Err( p_demux, "can't read xml stream" );
92             goto end;
93         }
94     } while( xml_ReaderNodeType( p_xml_reader ) != XML_READER_STARTELEM );
95
96     /* checking root node name */
97     psz_name = xml_ReaderName( p_xml_reader );
98     if( !psz_name || strcmp( psz_name, "plist" ) )
99     {
100         msg_Err( p_demux, "invalid root node name: %s", psz_name );
101         goto end;
102     }
103
104     xml_elem_hnd_t pl_elements[] =
105         { {"dict",    COMPLEX_CONTENT, {.cmplx = parse_plist_dict} } };
106     parse_plist_node( p_demux, p_current_input, NULL, p_xml_reader, "plist",
107                       pl_elements );
108
109     HANDLE_PLAY_AND_RELEASE;
110
111 end:
112     free( psz_name );
113     if( p_xml_reader )
114         xml_ReaderDelete( p_xml, p_xml_reader );
115     if( p_xml )
116         xml_Delete( p_xml );
117
118     /* Needed for correct operation of go back */
119     return 0;
120 }
121
122 /** \brief dummy function for demux callback interface */
123 static int Control( demux_t *p_demux, int i_query, va_list args )
124 {
125     VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args);
126     return VLC_EGENERIC;
127 }
128
129 /**
130  * \brief parse the root node of the playlist
131  */
132 static bool parse_plist_node( demux_t *p_demux, input_item_t *p_input_item,
133                               track_elem_t *p_track, xml_reader_t *p_xml_reader,
134                               const char *psz_element,
135                               xml_elem_hnd_t *p_handlers )
136 {
137     VLC_UNUSED(p_track); VLC_UNUSED(psz_element);
138     char *psz_name;
139     char *psz_value;
140     bool b_version_found = false;
141
142     /* read all playlist attributes */
143     while( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
144     {
145         psz_name = xml_ReaderName( p_xml_reader );
146         psz_value = xml_ReaderValue( p_xml_reader );
147         if( !psz_name || !psz_value )
148         {
149             msg_Err( p_demux, "invalid xml stream @ <plist>" );
150             free( psz_name );
151             free( psz_value );
152             return false;
153         }
154         /* attribute: version */
155         if( !strcmp( psz_name, "version" ) )
156         {
157             b_version_found = true;
158             if( strcmp( psz_value, "1.0" ) )
159                 msg_Warn( p_demux, "unsupported iTunes Media Library version" );
160         }
161         /* unknown attribute */
162         else
163             msg_Warn( p_demux, "invalid <plist> attribute:\"%s\"", psz_name);
164
165         free( psz_name );
166         free( psz_value );
167     }
168
169     /* attribute version is mandatory !!! */
170     if( !b_version_found )
171         msg_Warn( p_demux, "<plist> requires \"version\" attribute" );
172
173     return parse_dict( p_demux, p_input_item, NULL, p_xml_reader,
174                        "plist", p_handlers );
175 }
176
177 /**
178  * \brief parse a <dict>
179  * \param COMPLEX_INTERFACE
180  */
181 static bool parse_dict( demux_t *p_demux, input_item_t *p_input_item,
182                         track_elem_t *p_track, xml_reader_t *p_xml_reader,
183                         const char *psz_element, xml_elem_hnd_t *p_handlers )
184 {
185     int i_node;
186     char *psz_name = NULL;
187     char *psz_value = NULL;
188     char *psz_key = NULL;
189     xml_elem_hnd_t *p_handler = NULL;
190     bool b_ret = false;
191
192     while( xml_ReaderRead( p_xml_reader ) == 1 )
193     {
194         i_node = xml_ReaderNodeType( p_xml_reader );
195         switch( i_node )
196         {
197             case XML_READER_NONE:
198                 break;
199
200             case XML_READER_STARTELEM:
201                 /*  element start tag  */
202                 psz_name = xml_ReaderName( p_xml_reader );
203                 if( !psz_name || !*psz_name )
204                 {
205                     msg_Err( p_demux, "invalid xml stream" );
206                     goto end;
207                 }
208                 /* choose handler */
209                 for( p_handler = p_handlers;
210                      p_handler->name && strcmp( psz_name, p_handler->name );
211                      p_handler++ );
212                 if( !p_handler->name )
213                 {
214                     msg_Err( p_demux, "unexpected element <%s>", psz_name );
215                     goto end;
216                 }
217                 FREE_NAME();
218                 /* complex content is parsed in a separate function */
219                 if( p_handler->type == COMPLEX_CONTENT )
220                 {
221                     if( p_handler->pf_handler.cmplx( p_demux,
222                                                      p_input_item,
223                                                      NULL,
224                                                      p_xml_reader,
225                                                      p_handler->name,
226                                                      NULL ) )
227                     {
228                         p_handler = NULL;
229                         FREE_ATT_KEY();
230                     }
231                     else
232                     {
233                         goto end;
234                     }
235                 }
236                 break;
237
238             case XML_READER_TEXT:
239                 /* simple element content */
240                 free( psz_value );
241                 psz_value = xml_ReaderValue( p_xml_reader );
242                 if( !psz_value )
243                 {
244                     msg_Err( p_demux, "invalid xml stream" );
245                     goto end;
246                 }
247                 break;
248
249             case XML_READER_ENDELEM:
250                 /* element end tag */
251                 psz_name = xml_ReaderName( p_xml_reader );
252                 if( !psz_name )
253                 {
254                     msg_Err( p_demux, "invalid xml stream" );
255                     goto end;
256                 }
257                 /* leave if the current parent node <track> is terminated */
258                 if( !strcmp( psz_name, psz_element ) )
259                 {
260                     b_ret = true;
261                     goto end;
262                 }
263                 /* there MUST have been a start tag for that element name */
264                 if( !p_handler || !p_handler->name
265                     || strcmp( p_handler->name, psz_name ))
266                 {
267                     msg_Err( p_demux, "there's no open element left for <%s>",
268                              psz_name );
269                     goto end;
270                 }
271                 /* special case: key */
272                 if( !strcmp( p_handler->name, "key" ) )
273                 {
274                     psz_key = strdup( psz_value );
275                 }
276                 /* call the simple handler */
277                 else if( p_handler->pf_handler.smpl )
278                 {
279                     p_handler->pf_handler.smpl( p_track, psz_key, psz_value );
280                 }
281                 FREE_ATT();
282                 p_handler = NULL;
283                 break;
284
285             default:
286                 /* unknown/unexpected xml node */
287                 msg_Err( p_demux, "unexpected xml node %i", i_node );
288                 goto end;
289         }
290         FREE_NAME();
291     }
292     msg_Err( p_demux, "unexpected end of xml data" );
293
294 end:
295     free( psz_name );
296     free( psz_value );
297     free( psz_key );
298     return b_ret;
299 }
300
301 static bool parse_plist_dict( demux_t *p_demux, input_item_t *p_input_item,
302                               track_elem_t *p_track, xml_reader_t *p_xml_reader,
303                               const char *psz_element,
304                               xml_elem_hnd_t *p_handlers )
305 {
306     VLC_UNUSED(p_track); VLC_UNUSED(psz_element); VLC_UNUSED(p_handlers);
307     xml_elem_hnd_t pl_elements[] =
308         { {"dict",    COMPLEX_CONTENT, {.cmplx = parse_tracks_dict} },
309           {"array",   SIMPLE_CONTENT,  {NULL} },
310           {"key",     SIMPLE_CONTENT,  {NULL} },
311           {"integer", SIMPLE_CONTENT,  {NULL} },
312           {"string",  SIMPLE_CONTENT,  {NULL} },
313           {"date",    SIMPLE_CONTENT,  {NULL} },
314           {"true",    SIMPLE_CONTENT,  {NULL} },
315           {"false",   SIMPLE_CONTENT,  {NULL} },
316           {NULL,      UNKNOWN_CONTENT, {NULL} }
317         };
318
319     return parse_dict( p_demux, p_input_item, NULL, p_xml_reader,
320                        "dict", pl_elements );
321 }
322
323 static bool parse_tracks_dict( demux_t *p_demux, input_item_t *p_input_item,
324                                track_elem_t *p_track, xml_reader_t *p_xml_reader,
325                                const char *psz_element,
326                                xml_elem_hnd_t *p_handlers )
327 {
328     VLC_UNUSED(p_track); VLC_UNUSED(psz_element); VLC_UNUSED(p_handlers);
329     xml_elem_hnd_t tracks_elements[] =
330         { {"dict",    COMPLEX_CONTENT, {.cmplx = parse_track_dict} },
331           {"key",     SIMPLE_CONTENT,  {NULL} },
332           {NULL,      UNKNOWN_CONTENT, {NULL} }
333         };
334
335     parse_dict( p_demux, p_input_item, NULL, p_xml_reader,
336                 "dict", tracks_elements );
337
338     msg_Info( p_demux, "added %i tracks successfully",
339               p_demux->p_sys->i_ntracks );
340
341     return true;
342 }
343
344 static bool parse_track_dict( demux_t *p_demux, input_item_t *p_input_item,
345                               track_elem_t *p_track, xml_reader_t *p_xml_reader,
346                               const char *psz_element,
347                               xml_elem_hnd_t *p_handlers )
348 {
349     VLC_UNUSED(psz_element); VLC_UNUSED(p_handlers);
350     input_item_t *p_new_input = NULL;
351     int i_ret;
352     char *psz_uri = NULL;
353     p_track = new_track();
354
355     xml_elem_hnd_t track_elements[] =
356         { {"array",   COMPLEX_CONTENT, {.cmplx = skip_element} },
357           {"key",     SIMPLE_CONTENT,  {.smpl = save_data} },
358           {"integer", SIMPLE_CONTENT,  {.smpl = save_data} },
359           {"string",  SIMPLE_CONTENT,  {.smpl = save_data} },
360           {"date",    SIMPLE_CONTENT,  {.smpl = save_data} },
361           {"true",    SIMPLE_CONTENT,  {NULL} },
362           {"false",   SIMPLE_CONTENT,  {NULL} },
363           {NULL,      UNKNOWN_CONTENT, {NULL} }
364         };
365
366     i_ret = parse_dict( p_demux, p_input_item, p_track,
367                         p_xml_reader, "dict", track_elements );
368
369     msg_Dbg( p_demux, "name: %s, artist: %s, album: %s, genre: %s, trackNum: %s, location: %s",
370              p_track->name, p_track->artist, p_track->album, p_track->genre, p_track->trackNum, p_track->location );
371
372     if( !p_track->location )
373     {
374         msg_Err( p_demux, "Track needs Location" );
375         free_track( p_track );
376         return false;
377     }
378
379     psz_uri = decode_URI_duplicate( p_track->location );
380
381     if( psz_uri )
382     {
383         if( strlen( psz_uri ) > 17 &&
384             !strncmp( psz_uri, "file://localhost/", 17 ) )
385         {
386             /* remove 'localhost/' */
387             memmove( psz_uri + 7, psz_uri + 17, strlen( psz_uri ) - 9 );
388             msg_Info( p_demux, "Adding '%s'", psz_uri );
389
390             p_new_input = input_item_New( p_demux, psz_uri, NULL );
391             input_item_AddSubItem( p_input_item, p_new_input );
392
393             /* add meta info */
394             add_meta( p_new_input, p_track );
395             vlc_gc_decref( p_new_input );
396
397             p_demux->p_sys->i_ntracks++;
398         }
399         else
400         {
401             msg_Err( p_demux, "Don't know how to handle %s", psz_uri );
402         }
403         free( psz_uri );
404     }
405
406     free_track( p_track );
407     return i_ret;
408 }
409
410 static track_elem_t *new_track()
411 {
412     track_elem_t *p_track;
413     p_track = malloc( sizeof( track_elem_t ) );
414     if( p_track )
415     {
416         p_track->name = NULL;
417         p_track->artist = NULL;
418         p_track->album = NULL;
419         p_track->genre = NULL;
420         p_track->trackNum = NULL;
421         p_track->location = NULL;
422         p_track->duration = 0;
423     }
424     return p_track;
425 }
426
427 static void free_track( track_elem_t *p_track )
428 {
429     fprintf( stderr, "free track\n" );
430     if ( !p_track )
431         return;
432
433     FREENULL( p_track->name );
434     FREENULL( p_track->artist );
435     FREENULL( p_track->album );
436     FREENULL( p_track->genre );
437     FREENULL( p_track->trackNum );
438     FREENULL( p_track->location );
439     p_track->duration = 0;
440     free( p_track );
441 }
442
443 static bool save_data( track_elem_t *p_track, const char *psz_name,
444                        char *psz_value)
445 {
446     /* exit if setting is impossible */
447     if( !psz_name || !psz_value || !p_track )
448         return false;
449
450     /* re-convert xml special characters inside psz_value */
451     resolve_xml_special_chars( psz_value );
452
453 #define SAVE_INFO( name, value ) \
454     if( !strcmp( psz_name, name ) ) { p_track->value = strdup( psz_value ); }
455
456     SAVE_INFO( "Name", name )
457     else SAVE_INFO( "Artist", artist )
458     else SAVE_INFO( "Album", album )
459     else SAVE_INFO( "Genre", genre )
460     else SAVE_INFO( "Track Number", trackNum )
461     else SAVE_INFO( "Location", location )
462     else if( !strcmp( psz_name, "Total Time" ) )
463     {
464         long i_num = atol( psz_value );
465         p_track->duration = (mtime_t) i_num*1000;
466     }
467 #undef SAVE_INFO
468     return true;
469 }
470
471 /**
472  * \brief handles the supported <track> sub-elements
473  */
474 static bool add_meta( input_item_t *p_input_item, track_elem_t *p_track )
475 {
476     /* exit if setting is impossible */
477     if( !p_input_item || !p_track )
478         return false;
479
480 #define SET_INFO( type, prop ) \
481     if( p_track->prop ) {input_item_Set##type( p_input_item, p_track->prop );}
482     SET_INFO( Title, name )
483     SET_INFO( Artist, artist )
484     SET_INFO( Album, album )
485     SET_INFO( Genre, genre )
486     SET_INFO( TrackNum, trackNum )
487     SET_INFO( Duration, duration )
488 #undef SET_INFO
489     return true;
490 }
491
492 /**
493  * \brief skips complex element content that we can't manage
494  */
495 static bool skip_element( demux_t *p_demux, input_item_t *p_input_item,
496                           track_elem_t *p_track, xml_reader_t *p_xml_reader,
497                           const char *psz_element, xml_elem_hnd_t *p_handlers )
498 {
499     VLC_UNUSED(p_demux); VLC_UNUSED(p_input_item);
500     VLC_UNUSED(p_track); VLC_UNUSED(p_handlers);
501     char *psz_endname;
502
503     while( xml_ReaderRead( p_xml_reader ) == 1 )
504     {
505         if( xml_ReaderNodeType( p_xml_reader ) == XML_READER_ENDELEM )
506         {
507             psz_endname = xml_ReaderName( p_xml_reader );
508             if( !psz_endname )
509                 return false;
510             if( !strcmp( psz_element, psz_endname ) )
511             {
512                 free( psz_endname );
513                 return true;
514             }
515             else
516                 free( psz_endname );
517         }
518     }
519     return false;
520 }