]> git.sesse.net Git - vlc/blob - modules/services_discovery/upnp.cpp
UPNP: return NULL instead of 0 in xml_getChildElementValue.
[vlc] / modules / services_discovery / upnp.cpp
1 /*****************************************************************************
2  * Upnp.cpp :  UPnP discovery module (libupnp)
3  *****************************************************************************
4  * Copyright (C) 2004-2011 the VideoLAN team
5  * $Id$
6  *
7  * Authors: RĂ©mi Denis-Courmont <rem # videolan.org> (original plugin)
8  *          Christian Henz <henz # c-lab.de>
9  *          Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
10  *
11  * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26  *****************************************************************************/
27
28 #define __STDC_CONSTANT_MACROS 1
29
30 #undef PACKAGE_NAME
31 #ifdef HAVE_CONFIG_H
32 # include "config.h"
33 #endif
34
35 #include "upnp.hpp"
36
37 #include <vlc_plugin.h>
38 #include <vlc_services_discovery.h>
39
40 #include <assert.h>
41
42 // Constants
43 const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1";
44 const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1";
45
46 // VLC handle
47 struct services_discovery_sys_t
48 {
49     UpnpClient_Handle client_handle;
50     MediaServerList* p_server_list;
51     vlc_mutex_t callback_lock;
52 };
53
54 // VLC callback prototypes
55 static int Open( vlc_object_t* );
56 static void Close( vlc_object_t* );
57 VLC_SD_PROBE_HELPER( "upnp", "Universal Plug'n'Play", SD_CAT_LAN )
58
59 // Module descriptor
60
61 vlc_module_begin();
62     set_shortname( "UPnP" );
63     set_description( N_( "Universal Plug'n'Play" ) );
64     set_category( CAT_PLAYLIST );
65     set_subcategory( SUBCAT_PLAYLIST_SD );
66     set_capability( "services_discovery", 0 );
67     set_callbacks( Open, Close );
68
69     VLC_SD_PROBE_SUBMODULE
70 vlc_module_end();
71
72
73 // More prototypes...
74
75 static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data );
76
77 const char* xml_getChildElementValue( IXML_Element* p_parent,
78                                       const char*   psz_tag_name );
79
80 const char* xml_getChildElementAttributeValue( IXML_Element* p_parent,
81                                         const char* psz_tag_name_,
82                                         const char* psz_attribute_ );
83
84 IXML_Document* parseBrowseResult( IXML_Document* p_doc );
85
86
87 // VLC callbacks...
88
89 static int Open( vlc_object_t *p_this )
90 {
91     int i_res;
92     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
93     services_discovery_sys_t *p_sys  = ( services_discovery_sys_t * )
94             calloc( 1, sizeof( services_discovery_sys_t ) );
95
96     if( !( p_sd->p_sys = p_sys ) )
97         return VLC_ENOMEM;
98
99     i_res = UpnpInit( 0, 0 );
100     if( i_res != UPNP_E_SUCCESS )
101     {
102         msg_Err( p_sd, "%s", UpnpGetErrorMessage( i_res ) );
103         free( p_sys );
104         return VLC_EGENERIC;
105     }
106
107     p_sys->p_server_list = new MediaServerList( p_sd );
108     vlc_mutex_init( &p_sys->callback_lock );
109
110     i_res = UpnpRegisterClient( Callback, p_sd, &p_sys->client_handle );
111     if( i_res != UPNP_E_SUCCESS )
112     {
113         msg_Err( p_sd, "%s", UpnpGetErrorMessage( i_res ) );
114         Close( (vlc_object_t*) p_sd );
115         return VLC_EGENERIC;
116     }
117
118     i_res = UpnpSearchAsync( p_sys->client_handle, 5,
119             MEDIA_SERVER_DEVICE_TYPE, p_sd );
120     if( i_res != UPNP_E_SUCCESS )
121     {
122         msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
123         Close( (vlc_object_t*) p_sd );
124         return VLC_EGENERIC;
125     }
126
127     i_res = UpnpSetMaxContentLength( 262144 );
128     if( i_res != UPNP_E_SUCCESS )
129     {
130         msg_Err( p_sd, "Failed to set maximum content length: %s", UpnpGetErrorMessage( i_res ) );
131         Close( (vlc_object_t*) p_sd );
132         return VLC_EGENERIC;
133     }
134
135     return VLC_SUCCESS;
136 }
137
138 static void Close( vlc_object_t *p_this )
139 {
140     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
141
142     UpnpUnRegisterClient( p_sd->p_sys->client_handle );
143     UpnpFinish();
144
145     delete p_sd->p_sys->p_server_list;
146     vlc_mutex_destroy( &p_sd->p_sys->callback_lock );
147
148     free( p_sd->p_sys );
149 }
150
151 // XML utility functions:
152
153 // Returns the value of a child element, or 0 on error
154 const char* xml_getChildElementValue( IXML_Element* p_parent,
155                                       const char*   psz_tag_name_ )
156 {
157     if ( !p_parent ) return NULL;
158     if ( !psz_tag_name_ ) return NULL;
159
160     IXML_NodeList* p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name_ );
161     if ( !p_node_list ) return NULL;
162
163     IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
164     ixmlNodeList_free( p_node_list );
165     if ( !p_element ) return NULL;
166
167     IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
168     if ( !p_text_node ) return NULL;
169
170     return ixmlNode_getNodeValue( p_text_node );
171 }
172
173 const char* xml_getChildElementAttributeValue( IXML_Element* p_parent,
174                                         const char* psz_tag_name_,
175                                         const char* psz_attribute_ )
176 {
177     if ( !p_parent ) return NULL;
178     if ( !psz_tag_name_ ) return NULL;
179     if ( !psz_attribute_ ) return NULL;
180
181     IXML_NodeList* p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name_ );
182     if ( !p_node_list ) return NULL;
183
184     IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
185     ixmlNodeList_free( p_node_list );
186     if ( !p_element ) return NULL;
187
188     return ixmlElement_getAttribute( (IXML_Element*) p_element, psz_attribute_ );
189 }
190
191 // Extracts the result document from a SOAP response
192 IXML_Document* parseBrowseResult( IXML_Document* p_doc )
193 {
194     ixmlRelaxParser( 1 );
195
196     if ( !p_doc ) return 0;
197
198     IXML_NodeList* p_result_list = ixmlDocument_getElementsByTagName( p_doc,
199                                                                    "Result" );
200
201     if ( !p_result_list ) return 0;
202
203     IXML_Node* p_result_node = ixmlNodeList_item( p_result_list, 0 );
204
205     ixmlNodeList_free( p_result_list );
206
207     if ( !p_result_node ) return 0;
208
209     IXML_Node* p_text_node = ixmlNode_getFirstChild( p_result_node );
210     if ( !p_text_node ) return 0;
211
212     const char* psz_result_string = ixmlNode_getNodeValue( p_text_node );
213
214     IXML_Document* p_browse_doc = ixmlParseBuffer( psz_result_string );
215
216     return p_browse_doc;
217 }
218
219
220 // Handles all UPnP events
221 static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data )
222 {
223     services_discovery_t* p_sd = ( services_discovery_t* ) p_user_data;
224     services_discovery_sys_t* p_sys = p_sd->p_sys;
225     vlc_mutex_locker locker( &p_sys->callback_lock );
226
227     switch( event_type )
228     {
229     case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
230     case UPNP_DISCOVERY_SEARCH_RESULT:
231     {
232         struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
233
234         IXML_Document *p_description_doc = 0;
235
236         int i_res;
237         i_res = UpnpDownloadXmlDoc( p_discovery->Location, &p_description_doc );
238         if ( i_res != UPNP_E_SUCCESS )
239         {
240             msg_Warn( p_sd, "Could not download device description! "
241                             "Fetching data from %s failed: %s",
242                             p_discovery->Location, UpnpGetErrorMessage( i_res ) );
243             return i_res;
244         }
245
246         MediaServer::parseDeviceDescription( p_description_doc,
247                 p_discovery->Location, p_sd );
248
249         ixmlDocument_free( p_description_doc );
250     }
251     break;
252
253     case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
254     {
255         struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
256
257         p_sys->p_server_list->removeServer( p_discovery->DeviceId );
258
259     }
260     break;
261
262     case UPNP_EVENT_RECEIVED:
263     {
264         Upnp_Event* p_e = ( Upnp_Event* )p_event;
265
266         MediaServer* p_server = p_sys->p_server_list->getServerBySID( p_e->Sid );
267         if ( p_server ) p_server->fetchContents();
268     }
269     break;
270
271     case UPNP_EVENT_AUTORENEWAL_FAILED:
272     case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
273     {
274         // Re-subscribe...
275
276         Upnp_Event_Subscribe* p_s = ( Upnp_Event_Subscribe* )p_event;
277
278         MediaServer* p_server = p_sys->p_server_list->getServerBySID( p_s->Sid );
279         if ( p_server ) p_server->subscribeToContentDirectory();
280     }
281     break;
282
283     case UPNP_EVENT_SUBSCRIBE_COMPLETE:
284         msg_Warn( p_sd, "subscription complete" );
285         break;
286
287     case UPNP_DISCOVERY_SEARCH_TIMEOUT:
288         msg_Warn( p_sd, "search timeout" );
289         break;
290
291     default:
292         msg_Err( p_sd, "Unhandled event, please report ( type=%d )", event_type );
293         break;
294     }
295
296     return UPNP_E_SUCCESS;
297 }
298
299
300 // Class implementations...
301
302 // MediaServer...
303
304 void MediaServer::parseDeviceDescription( IXML_Document* p_doc,
305                                           const char*    p_location,
306                                           services_discovery_t* p_sd )
307 {
308     if ( !p_doc )
309     {
310         msg_Err( p_sd, "Null IXML_Document" );
311         return;
312     }
313
314     if ( !p_location )
315     {
316         msg_Err( p_sd, "Null location" );
317         return;
318     }
319
320     const char* psz_base_url = p_location;
321
322     // Try to extract baseURL
323     IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( p_doc, "baseURL" );
324     if ( p_url_list )
325     {
326
327         if ( IXML_Node* p_url_node = ixmlNodeList_item( p_url_list, 0 ) )
328         {
329             IXML_Node* p_text_node = ixmlNode_getFirstChild( p_url_node );
330             if ( p_text_node ) psz_base_url = ixmlNode_getNodeValue( p_text_node );
331         }
332
333         ixmlNodeList_free( p_url_list );
334     }
335
336     // Get devices
337     IXML_NodeList* p_device_list =
338                 ixmlDocument_getElementsByTagName( p_doc, "device" );
339
340     if ( p_device_list )
341     {
342         for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_list ); i++ )
343         {
344             IXML_Element* p_device_element =
345                    ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
346
347             const char* psz_device_type = xml_getChildElementValue( p_device_element,
348                                                                "deviceType" );
349             if ( !psz_device_type )
350             {
351                 msg_Warn( p_sd, "No deviceType found!" );
352                 continue;
353             }
354
355             if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type ) != 0 )
356                 continue;
357
358             const char* psz_udn = xml_getChildElementValue( p_device_element, "UDN" );
359             if ( !psz_udn )
360             {
361                 msg_Warn( p_sd, "No UDN!" );
362                 continue;
363             }
364
365             // Check if server is already added
366             if ( p_sd->p_sys->p_server_list->getServer( psz_udn ) != 0 )
367             {
368                 msg_Warn( p_sd, "Server with uuid '%s' already exists.", psz_udn );
369                 continue;
370             }
371
372             const char* psz_friendly_name =
373                        xml_getChildElementValue( p_device_element,
374                                                  "friendlyName" );
375
376             if ( !psz_friendly_name )
377             {
378                 msg_Dbg( p_sd, "No friendlyName!" );
379                 continue;
380             }
381
382             MediaServer* p_server = new MediaServer( psz_udn, psz_friendly_name, p_sd );
383
384             if ( !p_sd->p_sys->p_server_list->addServer( p_server ) )
385             {
386                 delete p_server;
387                 p_server = 0;
388                 continue;
389             }
390
391             // Check for ContentDirectory service...
392             IXML_NodeList* p_service_list =
393                        ixmlElement_getElementsByTagName( p_device_element,
394                                                          "service" );
395             if ( p_service_list )
396             {
397                 for ( unsigned int j = 0;
398                       j < ixmlNodeList_length( p_service_list ); j++ )
399                 {
400                     IXML_Element* p_service_element =
401                         ( IXML_Element* ) ixmlNodeList_item( p_service_list, j );
402
403                     const char* psz_service_type =
404                         xml_getChildElementValue( p_service_element,
405                                                   "serviceType" );
406                     if ( !psz_service_type )
407                     {
408                         msg_Warn( p_sd, "No service type found." );
409                         continue;
410                     }
411
412                     if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE,
413                                 psz_service_type ) != 0 )
414                         continue;
415
416                     const char* psz_event_sub_url =
417                         xml_getChildElementValue( p_service_element,
418                                                   "eventSubURL" );
419                     if ( !psz_event_sub_url )
420                     {
421                         msg_Warn( p_sd, "No event subscription url found." );
422                         continue;
423                     }
424
425                     const char* psz_control_url =
426                         xml_getChildElementValue( p_service_element,
427                                                   "controlURL" );
428                     if ( !psz_control_url )
429                     {
430                         msg_Warn( p_sd, "No control url found." );
431                         continue;
432                     }
433
434                     // Try to subscribe to ContentDirectory service
435
436                     char* psz_url = ( char* ) malloc( strlen( psz_base_url ) +
437                             strlen( psz_event_sub_url ) + 1 );
438                     if ( psz_url )
439                     {
440                         if ( UpnpResolveURL( psz_base_url, psz_event_sub_url, psz_url ) ==
441                                 UPNP_E_SUCCESS )
442                         {
443                             p_server->setContentDirectoryEventURL( psz_url );
444                             p_server->subscribeToContentDirectory();
445                         }
446
447                         free( psz_url );
448                     }
449
450                     // Try to browse content directory...
451
452                     psz_url = ( char* ) malloc( strlen( psz_base_url ) +
453                             strlen( psz_control_url ) + 1 );
454                     if ( psz_url )
455                     {
456                         if ( UpnpResolveURL( psz_base_url, psz_control_url, psz_url ) ==
457                                 UPNP_E_SUCCESS )
458                         {
459                             p_server->setContentDirectoryControlURL( psz_url );
460                             p_server->fetchContents();
461                         }
462
463                         free( psz_url );
464                     }
465                }
466                ixmlNodeList_free( p_service_list );
467            }
468        }
469        ixmlNodeList_free( p_device_list );
470     }
471 }
472
473 MediaServer::MediaServer( const char* psz_udn,
474                           const char* psz_friendly_name,
475                           services_discovery_t* p_sd )
476 {
477     _p_sd = p_sd;
478
479     _UDN = psz_udn;
480     _friendly_name = psz_friendly_name;
481
482     _p_contents = NULL;
483     _p_input_item = NULL;
484 }
485
486 MediaServer::~MediaServer()
487 {
488     delete _p_contents;
489 }
490
491 const char* MediaServer::getUDN() const
492 {
493   return _UDN.c_str();
494 }
495
496 const char* MediaServer::getFriendlyName() const
497 {
498     return _friendly_name.c_str();
499 }
500
501 void MediaServer::setContentDirectoryEventURL( const char* psz_url )
502 {
503     _content_directory_event_url = psz_url;
504 }
505
506 const char* MediaServer::getContentDirectoryEventURL() const
507 {
508     return _content_directory_event_url.c_str();
509 }
510
511 void MediaServer::setContentDirectoryControlURL( const char* psz_url )
512 {
513     _content_directory_control_url = psz_url;
514 }
515
516 const char* MediaServer::getContentDirectoryControlURL() const
517 {
518     return _content_directory_control_url.c_str();
519 }
520
521 void MediaServer::subscribeToContentDirectory()
522 {
523     const char* psz_url = getContentDirectoryEventURL();
524     if ( !psz_url )
525     {
526         msg_Dbg( _p_sd, "No subscription url set!" );
527         return;
528     }
529
530     int i_timeout = 1810;
531     Upnp_SID sid;
532
533     int i_res = UpnpSubscribe( _p_sd->p_sys->client_handle, psz_url, &i_timeout, sid );
534
535     if ( i_res == UPNP_E_SUCCESS )
536     {
537         _i_subscription_timeout = i_timeout;
538         memcpy( _subscription_id, sid, sizeof( Upnp_SID ) );
539     }
540     else
541     {
542         msg_Dbg( _p_sd, "Subscribe failed: '%s': %s",
543                 getFriendlyName(), UpnpGetErrorMessage( i_res ) );
544     }
545 }
546
547 IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
548                                            const char* psz_browser_flag_,
549                                            const char* psz_filter_,
550                                            const char* psz_starting_index_,
551                                            const char* psz_requested_count_,
552                                            const char* psz_sort_criteria_ )
553 {
554     IXML_Document* p_action = 0;
555     IXML_Document* p_response = 0;
556     const char* psz_url = getContentDirectoryControlURL();
557
558     if ( !psz_url )
559     {
560         msg_Dbg( _p_sd, "No subscription url set!" );
561         return 0;
562     }
563
564     char* psz_service_type = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
565
566     int i_res;
567
568     i_res = UpnpAddToAction( &p_action, "Browse",
569             psz_service_type, "ObjectID", psz_object_id_ );
570
571     if ( i_res != UPNP_E_SUCCESS )
572     {
573         msg_Dbg( _p_sd, "AddToAction 'ObjectID' failed: %s",
574                 UpnpGetErrorMessage( i_res ) );
575         goto browseActionCleanup;
576     }
577
578     i_res = UpnpAddToAction( &p_action, "Browse",
579             psz_service_type, "BrowseFlag", psz_browser_flag_ );
580
581     if ( i_res != UPNP_E_SUCCESS )
582     {
583         msg_Dbg( _p_sd, "AddToAction 'BrowseFlag' failed: %s", 
584                 UpnpGetErrorMessage( i_res ) );
585         goto browseActionCleanup;
586     }
587
588     i_res = UpnpAddToAction( &p_action, "Browse",
589             psz_service_type, "Filter", psz_filter_ );
590
591     if ( i_res != UPNP_E_SUCCESS )
592     {
593         msg_Dbg( _p_sd, "AddToAction 'Filter' failed: %s",
594                 UpnpGetErrorMessage( i_res ) );
595         goto browseActionCleanup;
596     }
597
598     i_res = UpnpAddToAction( &p_action, "Browse",
599             psz_service_type, "StartingIndex", psz_starting_index_ );
600
601     if ( i_res != UPNP_E_SUCCESS )
602     {
603         msg_Dbg( _p_sd, "AddToAction 'StartingIndex' failed: %s",
604                 UpnpGetErrorMessage( i_res ) );
605         goto browseActionCleanup;
606     }
607
608     i_res = UpnpAddToAction( &p_action, "Browse",
609             psz_service_type, "RequestedCount", psz_requested_count_ );
610
611     if ( i_res != UPNP_E_SUCCESS )
612     {
613         msg_Dbg( _p_sd, "AddToAction 'RequestedCount' failed: %s",
614                 UpnpGetErrorMessage( i_res ) );
615         goto browseActionCleanup;
616     }
617
618     i_res = UpnpAddToAction( &p_action, "Browse",
619             psz_service_type, "SortCriteria", psz_sort_criteria_ );
620
621     if ( i_res != UPNP_E_SUCCESS )
622     {
623         msg_Dbg( _p_sd, "AddToAction 'SortCriteria' failed: %s",
624                 UpnpGetErrorMessage( i_res ) );
625         goto browseActionCleanup;
626     }
627
628     i_res = UpnpSendAction( _p_sd->p_sys->client_handle,
629               psz_url,
630               psz_service_type,
631               0, // ignored in SDK, must be NULL
632               p_action,
633               &p_response );
634
635     if ( i_res != UPNP_E_SUCCESS )
636     {
637         msg_Err( _p_sd, "%s when trying the send() action with URL: %s",
638                 UpnpGetErrorMessage( i_res ), psz_url );
639
640         ixmlDocument_free( p_response );
641         p_response = 0;
642     }
643
644 browseActionCleanup:
645
646     free( psz_service_type );
647
648     ixmlDocument_free( p_action );
649     return p_response;
650 }
651
652 void MediaServer::fetchContents()
653 {
654     // Delete previous contents to prevent duplicate entries
655     if ( _p_contents )
656     {
657         delete _p_contents;
658         services_discovery_RemoveItem( _p_sd, _p_input_item );
659         services_discovery_AddItem( _p_sd, _p_input_item, NULL );
660     }
661
662     Container* root = new Container( 0, "0", getFriendlyName() );
663
664     _fetchContents( root );
665
666     _p_contents = root;
667     _p_contents->setInputItem( _p_input_item );
668
669     _buildPlaylist( _p_contents, NULL );
670 }
671
672 bool MediaServer::_fetchContents( Container* p_parent )
673 {
674     if (!p_parent)
675     {
676         msg_Err( _p_sd, "No parent" );
677         return false;
678     }
679
680     IXML_Document* p_response = _browseAction( p_parent->getObjectID(),
681                                       "BrowseDirectChildren",
682                                       "*", "0", "0", "" );
683     if ( !p_response )
684     {
685         msg_Err( _p_sd, "No response from browse() action" );
686         return false;
687     }
688
689     IXML_Document* p_result = parseBrowseResult( p_response );
690     ixmlDocument_free( p_response );
691
692     if ( !p_result )
693     {
694         msg_Err( _p_sd, "browse() response parsing failed" );
695         return false;
696     }
697 #ifndef NDEBUG
698     else
699     {
700         msg_Dbg( _p_sd, "Got DIDL document: %s",
701                 ixmlPrintDocument( p_result ) );
702     }
703 #endif
704
705     IXML_NodeList* containerNodeList =
706                 ixmlDocument_getElementsByTagName( p_result, "container" );
707
708     if ( containerNodeList )
709     {
710         for ( unsigned int i = 0;
711                 i < ixmlNodeList_length( containerNodeList ); i++ )
712         {
713             IXML_Element* containerElement =
714                   ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
715
716             const char* objectID = ixmlElement_getAttribute( containerElement,
717                                                              "id" );
718             if ( !objectID )
719                 continue;
720
721             const char* childCountStr =
722                     ixmlElement_getAttribute( containerElement, "childCount" );
723
724             if ( !childCountStr )
725                 continue;
726
727             int childCount = atoi( childCountStr );
728             const char* title = xml_getChildElementValue( containerElement,
729                                                           "dc:title" );
730
731             if ( !title )
732                 continue;
733
734             const char* resource = xml_getChildElementValue( containerElement,
735                                                              "res" );
736
737             if ( resource && childCount < 1 )
738             {
739                 Item* item = new Item( p_parent, objectID, title, resource, -1 );
740                 p_parent->addItem( item );
741             }
742
743             else
744             {
745                 Container* container = new Container( p_parent, objectID, title );
746                 p_parent->addContainer( container );
747
748                 if ( childCount > 0 )
749                     _fetchContents( container );
750             }
751         }
752         ixmlNodeList_free( containerNodeList );
753     }
754
755     IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( p_result,
756                                                                      "item" );
757     if ( itemNodeList )
758     {
759         for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
760         {
761             IXML_Element* itemElement =
762                         ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
763
764             const char* objectID =
765                         ixmlElement_getAttribute( itemElement, "id" );
766
767             if ( !objectID )
768                 continue;
769
770             const char* title =
771                         xml_getChildElementValue( itemElement, "dc:title" );
772
773             if ( !title )
774                 continue;
775
776             const char* resource =
777                         xml_getChildElementValue( itemElement, "res" );
778
779             if ( !resource )
780                 continue;
781
782             const char* psz_duration = xml_getChildElementAttributeValue( itemElement,
783                                                                     "res",
784                                                                     "duration" );
785
786             mtime_t i_duration = -1;
787             int i_hours, i_minutes, i_seconds, i_decis;
788
789             if ( psz_duration )
790             {
791                 if( sscanf( psz_duration, "%02d:%02d:%02d.%d",
792                         &i_hours, &i_minutes, &i_seconds, &i_decis ))
793                     i_duration = INT64_C(1000000) * ( i_hours*3600 +
794                                                       i_minutes*60 +
795                                                       i_seconds ) +
796                                  INT64_C(100000) * i_decis;
797             }
798
799             Item* item = new Item( p_parent, objectID, title, resource, i_duration );
800             p_parent->addItem( item );
801         }
802         ixmlNodeList_free( itemNodeList );
803     }
804
805     ixmlDocument_free( p_result );
806     return true;
807 }
808
809 // TODO: Create a permanent fix for the item duplication bug. The current fix
810 // is essentially only a small hack. Although it fixes the problem, it introduces
811 // annoying cosmetic issues with the playlist. For example, when the UPnP Server
812 // rebroadcasts it's directory structure, the VLC Client deletes the old directory
813 // structure, causing the user to go back to the root node of the directory. The
814 // directory is then rebuilt, and the user is forced to traverse through the directory
815 // to find the item they were looking for. Some servers may not push the directory
816 // structure too often, but we cannot rely on this fix.
817 //
818 // I have thought up another fix, but this would require certain features to
819 // be present within the VLC services discovery. Currently, services_discovery_AddItem
820 // does not allow the programmer to nest items. It only allows a "2 deep" scope.
821 // An example of the limitation is below:
822 //
823 // Root Directory
824 // + Item 1
825 // + Item 2
826 //
827 // services_discovery_AddItem will not let the programmer specify a child-node to
828 // insert items into, so we would not be able to do the following:
829 //
830 // Root Directory
831 // + Item 1
832 //   + Sub Item 1
833 // + Item 2
834 //   + Sub Item 1 of Item 2
835 //     + Sub-Sub Item 1 of Sub Item 1
836 //
837 // This creates a HUGE limitation on what we are able to do. If we were able to do
838 // the above, we could simply preserve the old directory listing, and compare what items
839 // do not exist in the new directory listing, then remove them from the shown listing using
840 // services_discovery_RemoveItem. If new files were introduced within an already existing
841 // container, we could simply do so with services_discovery_AddItem.
842 void MediaServer::_buildPlaylist( Container* p_parent, input_item_node_t *p_input_node )
843 {
844     bool b_send = p_input_node == NULL;
845     if( b_send )
846         p_input_node = input_item_node_Create( p_parent->getInputItem() );
847
848     for ( unsigned int i = 0; i < p_parent->getNumContainers(); i++ )
849     {
850         Container* p_container = p_parent->getContainer( i );
851
852         input_item_t* p_input_item = input_item_New( _p_sd, "vlc://nop",
853                                                     p_container->getTitle() );
854         input_item_node_t *p_new_node =
855             input_item_node_AppendItem( p_input_node, p_input_item );
856
857         p_container->setInputItem( p_input_item );
858         _buildPlaylist( p_container, p_new_node );
859     }
860
861     for ( unsigned int i = 0; i < p_parent->getNumItems(); i++ )
862     {
863         Item* p_item = p_parent->getItem( i );
864
865         input_item_t* p_input_item = input_item_NewExt( _p_sd,
866                                                p_item->getResource(),
867                                                p_item->getTitle(),
868                                                0,
869                                                NULL,
870                                                0,
871                                                p_item->getDuration() );
872
873         assert( p_input_item );
874         input_item_node_AppendItem( p_input_node, p_input_item );
875         p_item->setInputItem( p_input_item );
876     }
877
878     if( b_send )
879         input_item_node_PostAndDelete( p_input_node );
880 }
881
882 void MediaServer::setInputItem( input_item_t* p_input_item )
883 {
884     if( _p_input_item == p_input_item )
885         return;
886
887     if( _p_input_item )
888         vlc_gc_decref( _p_input_item );
889
890     vlc_gc_incref( p_input_item );
891     _p_input_item = p_input_item;
892 }
893
894 input_item_t* MediaServer::getInputItem() const
895 {
896     return _p_input_item;
897 }
898
899 bool MediaServer::compareSID( const char* psz_sid )
900 {
901     return ( strncmp( _subscription_id, psz_sid, sizeof( Upnp_SID ) ) == 0 );
902 }
903
904
905 // MediaServerList...
906
907 MediaServerList::MediaServerList( services_discovery_t* p_sd )
908 {
909     _p_sd = p_sd;
910 }
911
912 MediaServerList::~MediaServerList()
913 {
914     for ( unsigned int i = 0; i < _list.size(); i++ )
915     {
916         delete _list[i];
917     }
918 }
919
920 bool MediaServerList::addServer( MediaServer* p_server )
921 {
922     input_item_t* p_input_item = NULL;
923     if ( getServer( p_server->getUDN() ) != 0 ) return false;
924
925     msg_Dbg( _p_sd, "Adding server '%s' with uuid '%s'", p_server->getFriendlyName(), p_server->getUDN() );
926
927     p_input_item = input_item_New( _p_sd, "vlc://nop",
928                                   p_server->getFriendlyName() );
929     p_server->setInputItem( p_input_item );
930
931     services_discovery_AddItem( _p_sd, p_input_item, NULL );
932
933     _list.push_back( p_server );
934
935     return true;
936 }
937
938 MediaServer* MediaServerList::getServer( const char* psz_udn )
939 {
940     MediaServer* p_result = 0;
941
942     for ( unsigned int i = 0; i < _list.size(); i++ )
943     {
944         if( strcmp( psz_udn, _list[i]->getUDN() ) == 0 )
945         {
946             p_result = _list[i];
947             break;
948         }
949     }
950
951     return p_result;
952 }
953
954 MediaServer* MediaServerList::getServerBySID( const char* psz_sid )
955 {
956     MediaServer* p_server = 0;
957
958     for ( unsigned int i = 0; i < _list.size(); i++ )
959     {
960         if ( _list[i]->compareSID( psz_sid ) )
961         {
962             p_server = _list[i];
963             break;
964         }
965     }
966
967     return p_server;
968 }
969
970 void MediaServerList::removeServer( const char* psz_udn )
971 {
972     MediaServer* p_server = getServer( psz_udn );
973     if ( !p_server ) return;
974
975     msg_Dbg( _p_sd, "Removing server '%s'", p_server->getFriendlyName() );
976
977     services_discovery_RemoveItem( _p_sd, p_server->getInputItem() );
978
979     std::vector<MediaServer*>::iterator it;
980     for ( it = _list.begin(); it != _list.end(); ++it )
981     {
982         if ( *it == p_server )
983         {
984             _list.erase( it );
985             delete p_server;
986             break;
987         }
988     }
989 }
990
991
992 // Item...
993
994 Item::Item( Container* p_parent, const char* psz_object_id, const char* psz_title,
995            const char* psz_resource, mtime_t i_duration )
996 {
997     _parent = p_parent;
998
999     _objectID = psz_object_id;
1000     _title = psz_title;
1001     _resource = psz_resource;
1002     _duration = i_duration;
1003
1004     _p_input_item = NULL;
1005 }
1006
1007 Item::~Item()
1008 {
1009     if( _p_input_item )
1010         vlc_gc_decref( _p_input_item );
1011 }
1012
1013 const char* Item::getObjectID() const
1014 {
1015     return _objectID.c_str();
1016 }
1017
1018 const char* Item::getTitle() const
1019 {
1020     return _title.c_str();
1021 }
1022
1023 const char* Item::getResource() const
1024 {
1025     return _resource.c_str();
1026 }
1027
1028 const mtime_t Item::getDuration() const
1029 {
1030     return _duration;
1031 }
1032
1033 void Item::setInputItem( input_item_t* p_input_item )
1034 {
1035     if( _p_input_item == p_input_item )
1036         return;
1037
1038     if( _p_input_item )
1039         vlc_gc_decref( _p_input_item );
1040
1041     vlc_gc_incref( p_input_item );
1042     _p_input_item = p_input_item;
1043 }
1044
1045 // Container...
1046
1047 Container::Container( Container*  p_parent,
1048                       const char* psz_object_id,
1049                       const char* psz_title )
1050 {
1051     _parent = p_parent;
1052
1053     _objectID = psz_object_id;
1054     _title = psz_title;
1055
1056     _p_input_item = NULL;
1057 }
1058
1059 Container::~Container()
1060 {
1061     for ( unsigned int i = 0; i < _containers.size(); i++ )
1062     {
1063         delete _containers[i];
1064     }
1065
1066     for ( unsigned int i = 0; i < _items.size(); i++ )
1067     {
1068         delete _items[i];
1069     }
1070
1071     if( _p_input_item )
1072         vlc_gc_decref( _p_input_item );
1073 }
1074
1075 void Container::addItem( Item* item )
1076 {
1077     _items.push_back( item );
1078 }
1079
1080 void Container::addContainer( Container* p_container )
1081 {
1082     _containers.push_back( p_container );
1083 }
1084
1085 const char* Container::getObjectID() const
1086 {
1087     return _objectID.c_str();
1088 }
1089
1090 const char* Container::getTitle() const
1091 {
1092     return _title.c_str();
1093 }
1094
1095 unsigned int Container::getNumItems() const
1096 {
1097     return _items.size();
1098 }
1099
1100 unsigned int Container::getNumContainers() const
1101 {
1102     return _containers.size();
1103 }
1104
1105 Item* Container::getItem( unsigned int i_index ) const
1106 {
1107     if ( i_index < _items.size() ) return _items[i_index];
1108     return 0;
1109 }
1110
1111 Container* Container::getContainer( unsigned int i_index ) const
1112 {
1113     if ( i_index < _containers.size() ) return _containers[i_index];
1114     return 0;
1115 }
1116
1117 Container* Container::getParent()
1118 {
1119     return _parent;
1120 }
1121
1122 void Container::setInputItem( input_item_t* p_input_item )
1123 {
1124     if( _p_input_item == p_input_item )
1125         return;
1126
1127     if( _p_input_item )
1128         vlc_gc_decref( _p_input_item );
1129
1130     vlc_gc_incref( p_input_item );
1131     _p_input_item = p_input_item;
1132 }
1133
1134 input_item_t* Container::getInputItem() const
1135 {
1136     return _p_input_item;
1137 }