]> git.sesse.net Git - vlc/blob - modules/services_discovery/upnp_intel.cpp
Use var_InheritString for --decklink-video-connection.
[vlc] / modules / services_discovery / upnp_intel.cpp
1 /*****************************************************************************
2  * Upnp_intel.cpp :  UPnP discovery module (Intel SDK)
3  *****************************************************************************
4  * Copyright (C) 2004-2008 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 /*
29   \TODO: Debug messages: "__FILE__, __LINE__" ok ???, Wrn/Err ???
30 */
31 #undef PACKAGE_NAME
32 #ifdef HAVE_CONFIG_H
33 # include "config.h"
34 #endif
35
36 #include "upnp_intel.hpp"
37
38 #include <vlc_plugin.h>
39 #include <vlc_services_discovery.h>
40
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_intel", "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 IXML_Document* parseBrowseResult( IXML_Document* p_doc );
81
82
83 // VLC callbacks...
84
85 static int Open( vlc_object_t *p_this )
86 {
87     int i_res;
88     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
89     services_discovery_sys_t *p_sys  = ( services_discovery_sys_t * )
90             calloc( 1, sizeof( services_discovery_sys_t ) );
91
92     if(!(p_sd->p_sys = p_sys))
93         return VLC_ENOMEM;
94
95     i_res = UpnpInit( 0, 0 );
96     if( i_res != UPNP_E_SUCCESS )
97     {
98         msg_Err( p_sd, "%s", UpnpGetErrorMessage( i_res ) );
99         free( p_sys );
100         return VLC_EGENERIC;
101     }
102
103     p_sys->p_server_list = new MediaServerList( p_sd );
104     vlc_mutex_init( &p_sys->callback_lock );
105
106     i_res = UpnpRegisterClient( Callback, p_sd, &p_sys->client_handle );
107     if( i_res != UPNP_E_SUCCESS )
108     {
109         msg_Err( p_sd, "%s", UpnpGetErrorMessage( i_res ) );
110         Close( (vlc_object_t*) p_sd );
111         return VLC_EGENERIC;
112     }
113
114     i_res = UpnpSearchAsync( p_sys->client_handle, 5,
115             MEDIA_SERVER_DEVICE_TYPE, p_sd );
116
117     if( i_res != UPNP_E_SUCCESS )
118     {
119         msg_Err( p_sd, "%s", UpnpGetErrorMessage( i_res ) );
120         Close( (vlc_object_t*) p_sd );
121         return VLC_EGENERIC;
122     }
123
124     i_res = UpnpSetMaxContentLength( 262144 );
125     if( i_res != UPNP_E_SUCCESS )
126     {
127         msg_Err( p_sd, "%s", UpnpGetErrorMessage( i_res ) );
128         Close( (vlc_object_t*) p_sd );
129         return VLC_EGENERIC;
130     }
131
132     return VLC_SUCCESS;
133 }
134
135 static void Close( vlc_object_t *p_this )
136 {
137     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
138
139     UpnpFinish();
140     delete p_sd->p_sys->p_server_list;
141     vlc_mutex_destroy( &p_sd->p_sys->callback_lock );
142
143     free( p_sd->p_sys );
144 }
145
146 // XML utility functions:
147
148 // Returns the value of a child element, or 0 on error
149 const char* xml_getChildElementValue( IXML_Element* p_parent,
150                                       const char*   psz_tag_name_ )
151 {
152     if ( !p_parent ) return 0;
153     if ( !psz_tag_name_ ) return 0;
154
155     char* psz_tag_name = strdup( psz_tag_name_ );
156     IXML_NodeList* p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name );
157     free( psz_tag_name );
158     if ( !p_node_list ) return 0;
159
160     IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
161     ixmlNodeList_free( p_node_list );
162     if ( !p_element ) return 0;
163
164     IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
165     if ( !p_text_node ) return 0;
166
167     return ixmlNode_getNodeValue( p_text_node );
168 }
169
170 // Extracts the result document from a SOAP response
171 IXML_Document* parseBrowseResult( IXML_Document* p_doc )
172 {
173     ixmlRelaxParser(1);
174
175     if ( !p_doc ) return 0;
176
177     IXML_NodeList* p_result_list = ixmlDocument_getElementsByTagName( p_doc,
178                                                                    "Result" );
179
180     if ( !p_result_list ) return 0;
181
182     IXML_Node* p_result_node = ixmlNodeList_item( p_result_list, 0 );
183
184     ixmlNodeList_free( p_result_list );
185
186     if ( !p_result_node ) return 0;
187
188     IXML_Node* p_text_node = ixmlNode_getFirstChild( p_result_node );
189     if ( !p_text_node ) return 0;
190
191     const char* psz_result_string = ixmlNode_getNodeValue( p_text_node );
192     char* psz_result_xml = strdup( psz_result_string );
193
194     IXML_Document* p_browse_doc = ixmlParseBuffer( psz_result_xml );
195
196     free( psz_result_xml );
197
198     return p_browse_doc;
199 }
200
201
202 // Handles all UPnP events
203 static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data )
204 {
205     services_discovery_t* p_sd = ( services_discovery_t* ) p_user_data;
206     services_discovery_sys_t* p_sys = p_sd->p_sys;
207     vlc_mutex_locker locker( &p_sys->callback_lock );
208
209     switch( event_type )
210     {
211     case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
212     case UPNP_DISCOVERY_SEARCH_RESULT:
213     {
214         struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
215
216         IXML_Document *p_description_doc = 0;
217
218         int i_res;
219         i_res = UpnpDownloadXmlDoc( p_discovery->Location, &p_description_doc );
220         if ( i_res != UPNP_E_SUCCESS )
221         {
222             msg_Dbg( p_sd,
223                     "%s:%d: Could not download device description!",
224                     __FILE__, __LINE__ );
225             return i_res;
226         }
227
228         MediaServer::parseDeviceDescription( p_description_doc,
229                 p_discovery->Location, p_sd );
230
231         ixmlDocument_free( p_description_doc );
232     }
233     break;
234
235     case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
236     {
237         struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
238
239         p_sys->p_server_list->removeServer( p_discovery->DeviceId );
240     }
241     break;
242
243     case UPNP_EVENT_RECEIVED:
244     {
245         Upnp_Event* p_e = ( Upnp_Event* )p_event;
246
247         MediaServer* p_server = p_sys->p_server_list->getServerBySID( p_e->Sid );
248         if ( p_server ) p_server->fetchContents();
249     }
250     break;
251
252     case UPNP_EVENT_AUTORENEWAL_FAILED:
253     case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
254     {
255         // Re-subscribe...
256
257         Upnp_Event_Subscribe* p_s = ( Upnp_Event_Subscribe* )p_event;
258
259         MediaServer* p_server = p_sys->p_server_list->getServerBySID( p_s->Sid );
260         if ( p_server ) p_server->subscribeToContentDirectory();
261     }
262     break;
263
264     case UPNP_EVENT_SUBSCRIBE_COMPLETE:
265         msg_Warn( p_sd, "subscription complete" );
266         break;
267
268     case UPNP_DISCOVERY_SEARCH_TIMEOUT:
269         msg_Warn( p_sd, "search timeout" );
270         break;
271
272     default:
273     msg_Dbg( p_sd,
274             "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )",
275             __FILE__, __LINE__, event_type );
276     break;
277     }
278
279     return UPNP_E_SUCCESS;
280 }
281
282
283 // Class implementations...
284
285 // MediaServer...
286
287 void MediaServer::parseDeviceDescription( IXML_Document* p_doc,
288                                           const char*    p_location,
289                                           services_discovery_t* p_sd )
290 {
291     if ( !p_doc )
292     {
293         msg_Dbg( p_sd, "%s:%d: NULL", __FILE__, __LINE__ );
294         return;
295     }
296
297     if ( !p_location )
298     {
299         msg_Dbg( p_sd, "%s:%d: NULL", __FILE__, __LINE__ );
300         return;
301     }
302
303     const char* psz_base_url = p_location;
304
305     // Try to extract baseURL
306
307     IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( p_doc, "baseURL" );
308     if ( !p_url_list )
309     {
310
311         if ( IXML_Node* p_url_node = ixmlNodeList_item( p_url_list, 0 ) )
312         {
313             IXML_Node* p_text_node = ixmlNode_getFirstChild( p_url_node );
314             if ( p_text_node ) psz_base_url = ixmlNode_getNodeValue( p_text_node );
315         }
316
317         ixmlNodeList_free( p_url_list );
318     }
319
320     // Get devices
321
322     IXML_NodeList* p_device_list =
323                 ixmlDocument_getElementsByTagName( p_doc, "device" );
324
325     if ( p_device_list )
326     {
327         for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_list ); i++ )
328         {
329             IXML_Element* p_device_element =
330                    ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
331
332             const char* psz_device_type = xml_getChildElementValue( p_device_element,
333                                                                "deviceType" );
334             if ( !psz_device_type )
335             {
336                 msg_Dbg( p_sd,
337                         "%s:%d: no deviceType!",
338                         __FILE__, __LINE__ );
339                 continue;
340             }
341
342             if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type ) != 0 )
343                 continue;
344
345             const char* psz_udn = xml_getChildElementValue( p_device_element, "UDN" );
346             if ( !psz_udn )
347             {
348                 msg_Dbg( p_sd, "%s:%d: no UDN!",
349                         __FILE__, __LINE__ );
350                 continue;
351             }
352
353             if ( p_sd->p_sys->p_server_list->getServer( psz_udn ) != 0 )
354                 continue;
355
356             const char* psz_friendly_name =
357                        xml_getChildElementValue( p_device_element,
358                                                  "friendlyName" );
359
360             if ( !psz_friendly_name )
361             {
362                 msg_Dbg( p_sd, "%s:%d: no friendlyName!", __FILE__, __LINE__ );
363                 continue;
364             }
365
366             MediaServer* p_server = new MediaServer( psz_udn, psz_friendly_name, p_sd );
367
368             if ( !p_sd->p_sys->p_server_list->addServer( p_server ) )
369             {
370
371                 delete p_server;
372                 p_server = 0;
373                 continue;
374             }
375
376             // Check for ContentDirectory service...
377             IXML_NodeList* p_service_list =
378                        ixmlElement_getElementsByTagName( p_device_element,
379                                                          "service" );
380             if ( p_service_list )
381             {
382                 for ( unsigned int j = 0;
383                       j < ixmlNodeList_length( p_service_list ); j++ )
384                 {
385                     IXML_Element* p_service_element =
386                         ( IXML_Element* ) ixmlNodeList_item( p_service_list, j );
387
388                     const char* psz_service_type =
389                         xml_getChildElementValue( p_service_element,
390                                                   "serviceType" );
391                     if ( !psz_service_type )
392                         continue;
393
394                     if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE,
395                                 psz_service_type ) != 0 )
396                         continue;
397
398                     const char* psz_event_sub_url =
399                         xml_getChildElementValue( p_service_element,
400                                                   "eventSubURL" );
401                     if ( !psz_event_sub_url )
402                         continue;
403
404                     const char* psz_control_url =
405                         xml_getChildElementValue( p_service_element,
406                                                   "controlURL" );
407                     if ( !psz_control_url )
408                         continue;
409
410                     // Try to subscribe to ContentDirectory service
411
412                     char* psz_url = ( char* ) malloc( strlen( psz_base_url ) +
413                             strlen( psz_event_sub_url ) + 1 );
414                     if ( psz_url )
415                     {
416                         char* psz_s1 = strdup( psz_base_url );
417                         char* psz_s2 = strdup( psz_event_sub_url );
418
419                         if ( UpnpResolveURL( psz_s1, psz_s2, psz_url ) ==
420                                 UPNP_E_SUCCESS )
421                         {
422                             p_server->setContentDirectoryEventURL( psz_url );
423                             p_server->subscribeToContentDirectory();
424                         }
425
426                         free( psz_s1 );
427                         free( psz_s2 );
428                         free( psz_url );
429                     }
430
431                     // Try to browse content directory...
432
433                     psz_url = ( char* ) malloc( strlen( psz_base_url ) +
434                             strlen( psz_control_url ) + 1 );
435                     if ( psz_url )
436                     {
437                         char* psz_s1 = strdup( psz_base_url );
438                         char* psz_s2 = strdup( psz_control_url );
439
440                         if ( UpnpResolveURL( psz_s1, psz_s2, psz_url ) ==
441                                 UPNP_E_SUCCESS )
442                         {
443                             p_server->setContentDirectoryControlURL( psz_url );
444                             p_server->fetchContents();
445                         }
446
447                         free( psz_s1 );
448                         free( psz_s2 );
449                         free( psz_url );
450                     }
451                }
452                ixmlNodeList_free( p_service_list );
453            }
454        }
455        ixmlNodeList_free( p_device_list );
456     }
457 }
458
459 MediaServer::MediaServer( const char* psz_udn,
460                           const char* psz_friendly_name,
461                           services_discovery_t* p_sd )
462 {
463     _p_sd = p_sd;
464
465     _UDN = psz_udn;
466     _friendlyName = psz_friendly_name;
467
468     _contents = NULL;
469     _inputItem = NULL;
470 }
471
472 MediaServer::~MediaServer()
473 {
474     delete _contents;
475 }
476
477 const char* MediaServer::getUDN() const
478 {
479   const char* s = _UDN.c_str();
480   return s;
481 }
482
483 const char* MediaServer::getFriendlyName() const
484 {
485     const char* s = _friendlyName.c_str();
486     return s;
487 }
488
489 void MediaServer::setContentDirectoryEventURL( const char* psz_url )
490 {
491     _contentDirectoryEventURL = psz_url;
492 }
493
494 const char* MediaServer::getContentDirectoryEventURL() const
495 {
496     const char* s =  _contentDirectoryEventURL.c_str();
497     return s;
498 }
499
500 void MediaServer::setContentDirectoryControlURL( const char* psz_url )
501 {
502     _contentDirectoryControlURL = psz_url;
503 }
504
505 const char* MediaServer::getContentDirectoryControlURL() const
506 {
507     return _contentDirectoryControlURL.c_str();
508 }
509
510 void MediaServer::subscribeToContentDirectory()
511 {
512     const char* psz_url = getContentDirectoryEventURL();
513     if ( !psz_url || strcmp( psz_url, "" ) == 0 )
514     {
515         msg_Dbg( _p_sd, "No subscription url set!" );
516         return;
517     }
518
519     int i_timeout = 1810;
520     Upnp_SID sid;
521
522     int i_res = UpnpSubscribe( _p_sd->p_sys->client_handle, psz_url, &i_timeout, sid );
523
524     if ( i_res == UPNP_E_SUCCESS )
525     {
526         _subscriptionTimeOut = i_timeout;
527         memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
528     }
529     else
530     {
531         msg_Dbg( _p_sd,
532                 "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__,
533                 getFriendlyName(), UpnpGetErrorMessage( i_res ) );
534     }
535 }
536
537 IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
538                                            const char* psz_browser_flag_,
539                                            const char* psz_filter_,
540                                            const char* psz_starting_index_,
541                                            const char* psz_requested_count_,
542                                            const char* psz_sort_criteria_ )
543 {
544     IXML_Document* p_action = 0;
545     IXML_Document* p_response = 0;
546     const char* psz_url = getContentDirectoryControlURL();
547
548     if ( !psz_url || strcmp( psz_url, "" ) == 0 )
549     {
550         msg_Dbg( _p_sd, "No subscription url set!" );
551         return 0;
552     }
553
554     char* psz_object_id = strdup( psz_object_id_ );
555     char* psz_browse_flag = strdup( psz_browser_flag_ );
556     char* psz_filter = strdup( psz_filter_ );
557     char* psz_starting_index = strdup( psz_starting_index_ );
558     char* psz_requested_count = strdup( psz_requested_count_ );
559     char* psz_sort_criteria = strdup( psz_sort_criteria_ );
560     char* psz_service_type = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
561
562     int i_res;
563
564     i_res = UpnpAddToAction( &p_action, "Browse",
565             psz_service_type, "ObjectID", psz_object_id );
566
567     if ( i_res != UPNP_E_SUCCESS )
568     {
569         msg_Dbg( _p_sd,
570                  "%s:%d: ERROR: %s", __FILE__, __LINE__,
571                  UpnpGetErrorMessage( i_res ) );
572         goto browseActionCleanup;
573     }
574
575     i_res = UpnpAddToAction( &p_action, "Browse",
576             psz_service_type, "BrowseFlag", psz_browse_flag );
577
578     if ( i_res != UPNP_E_SUCCESS )
579     {
580         msg_Dbg( _p_sd,
581              "%s:%d: ERROR: %s", __FILE__, __LINE__,
582              UpnpGetErrorMessage( i_res ) );
583         goto browseActionCleanup;
584     }
585
586     i_res = UpnpAddToAction( &p_action, "Browse",
587             psz_service_type, "Filter", psz_filter );
588
589     if ( i_res != UPNP_E_SUCCESS )
590     {
591         msg_Dbg( _p_sd,
592              "%s:%d: ERROR: %s", __FILE__, __LINE__,
593              UpnpGetErrorMessage( i_res ) );
594         goto browseActionCleanup;
595     }
596
597     i_res = UpnpAddToAction( &p_action, "Browse",
598             psz_service_type, "StartingIndex", psz_starting_index );
599
600     if ( i_res != UPNP_E_SUCCESS )
601     {
602         msg_Dbg( _p_sd,
603              "%s:%d: ERROR: %s", __FILE__, __LINE__,
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,
614                 "%s:%d: ERROR: %s", __FILE__, __LINE__,
615                 UpnpGetErrorMessage( i_res ) ); goto browseActionCleanup; }
616
617     i_res = UpnpAddToAction( &p_action, "Browse",
618             psz_service_type, "SortCriteria", psz_sort_criteria );
619
620     if ( i_res != UPNP_E_SUCCESS )
621     {
622         msg_Dbg( _p_sd,
623              "%s:%d: ERROR: %s", __FILE__, __LINE__,
624              UpnpGetErrorMessage( i_res ) );
625         goto browseActionCleanup;
626     }
627
628     i_res = UpnpSendAction( _p_sd->p_sys->client_handle,
629               psz_url,
630               CONTENT_DIRECTORY_SERVICE_TYPE,
631               0,
632               p_action,
633               &p_response );
634
635     if ( i_res != UPNP_E_SUCCESS )
636     {
637         msg_Dbg( _p_sd,
638                 "%s:%d: ERROR: %s when trying the send() action with URL: %s",
639                 __FILE__, __LINE__,
640                 UpnpGetErrorMessage( i_res ), psz_url );
641
642         ixmlDocument_free( p_response );
643         p_response = 0;
644     }
645
646  browseActionCleanup:
647
648     free( psz_object_id );
649     free( psz_browse_flag );
650     free( psz_filter );
651     free( psz_starting_index );
652     free( psz_requested_count );
653     free( psz_sort_criteria );
654
655     free( psz_service_type );
656
657     ixmlDocument_free( p_action );
658     return p_response;
659 }
660
661 void MediaServer::fetchContents()
662 {
663     Container* root = new Container( 0, "0", getFriendlyName() );
664     _fetchContents( root );
665
666     _contents = root;
667     _contents->setInputItem( _inputItem );
668
669     _buildPlaylist( _contents, NULL );
670 }
671
672 bool MediaServer::_fetchContents( Container* p_parent )
673 {
674     if (!p_parent)
675     {
676         msg_Dbg( _p_sd,
677                 "%s:%d: parent==NULL", __FILE__, __LINE__ );
678         return false;
679     }
680
681     IXML_Document* p_response = _browseAction( p_parent->getObjectID(),
682                                       "BrowseDirectChildren",
683                                       "*", "0", "0", "" );
684     if ( !p_response )
685     {
686         msg_Dbg( _p_sd,
687                 "%s:%d: ERROR! No response from browse() action",
688                 __FILE__, __LINE__ );
689         return false;
690     }
691
692     IXML_Document* result = parseBrowseResult( p_response );
693     ixmlDocument_free( p_response );
694
695     if ( !result )
696     {
697         msg_Dbg( _p_sd,
698                 "%s:%d: ERROR! browse() response parsing failed",
699                 __FILE__, __LINE__ );
700         return false;
701     }
702
703     IXML_NodeList* containerNodeList =
704                 ixmlDocument_getElementsByTagName( result, "container" );
705
706     if ( containerNodeList )
707     {
708         for ( unsigned int i = 0;
709                 i < ixmlNodeList_length( containerNodeList ); i++ )
710         {
711             IXML_Element* containerElement =
712                   ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
713
714             const char* objectID = ixmlElement_getAttribute( containerElement,
715                                                              "id" );
716             if ( !objectID )
717                 continue;
718
719             const char* childCountStr =
720                     ixmlElement_getAttribute( containerElement, "childCount" );
721
722             if ( !childCountStr )
723                 continue;
724
725             int childCount = atoi( childCountStr );
726             const char* title = xml_getChildElementValue( containerElement,
727                                                           "dc:title" );
728
729             if ( !title )
730                 continue;
731
732             const char* resource = xml_getChildElementValue( containerElement,
733                                                              "res" );
734
735             if ( resource && childCount < 1 )
736             {
737                 Item* item = new Item( p_parent, objectID, title, resource );
738                 p_parent->addItem( item );
739             }
740
741             else
742             {
743                 Container* container = new Container( p_parent, objectID, title );
744                 p_parent->addContainer( container );
745
746                 if ( childCount > 0 )
747                     _fetchContents( container );
748             }
749         }
750         ixmlNodeList_free( containerNodeList );
751     }
752
753     IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result,
754                                                                      "item" );
755     if ( itemNodeList )
756     {
757         for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
758         {
759             IXML_Element* itemElement =
760                         ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
761
762             const char* objectID =
763                         ixmlElement_getAttribute( itemElement, "id" );
764
765             if ( !objectID )
766                 continue;
767
768             const char* title =
769                         xml_getChildElementValue( itemElement, "dc:title" );
770
771             if ( !title )
772                 continue;
773
774             const char* resource =
775                         xml_getChildElementValue( itemElement, "res" );
776
777             if ( !resource )
778                 continue;
779
780             Item* item = new Item( p_parent, objectID, title, resource );
781             p_parent->addItem( item );
782         }
783         ixmlNodeList_free( itemNodeList );
784     }
785
786     ixmlDocument_free( result );
787     return true;
788 }
789
790 void MediaServer::_buildPlaylist( Container* p_parent, input_item_node_t *p_input_node )
791 {
792     bool send = p_input_node == NULL;
793     if( send )
794         p_input_node = input_item_node_Create( p_parent->getInputItem() );
795
796     for ( unsigned int i = 0; i < p_parent->getNumContainers(); i++ )
797     {
798         Container* container = p_parent->getContainer( i );
799
800         input_item_t* p_input_item = input_item_New( _p_sd, "vlc://nop", container->getTitle() );
801         input_item_node_t *p_new_node =
802             input_item_node_AppendItem( p_input_node, p_input_item );
803
804         container->setInputItem( p_input_item );
805         _buildPlaylist( container, p_new_node );
806     }
807
808     for ( unsigned int i = 0; i < p_parent->getNumItems(); i++ )
809     {
810         Item* item = p_parent->getItem( i );
811
812         input_item_t* p_input_item = input_item_New( _p_sd,
813                                                item->getResource(),
814                                                item->getTitle() );
815         assert( p_input_item );
816         input_item_node_AppendItem( p_input_node, p_input_item );
817         item->setInputItem( p_input_item );
818     }
819
820     if( send )
821         input_item_node_PostAndDelete( p_input_node );
822 }
823
824 void MediaServer::setInputItem( input_item_t* p_input_item )
825 {
826     if(_inputItem == p_input_item)
827         return;
828
829     if(_inputItem)
830         vlc_gc_decref( _inputItem );
831
832     vlc_gc_incref( p_input_item );
833     _inputItem = p_input_item;
834 }
835
836 bool MediaServer::compareSID( const char* sid )
837 {
838     return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 );
839 }
840
841
842 // MediaServerList...
843
844 MediaServerList::MediaServerList( services_discovery_t* p_sd )
845 {
846     _p_sd = p_sd;
847 }
848
849 MediaServerList::~MediaServerList()
850 {
851     for ( unsigned int i = 0; i < _list.size(); i++ )
852     {
853         delete _list[i];
854     }
855 }
856
857 bool MediaServerList::addServer( MediaServer* s )
858 {
859     input_item_t* p_input_item = NULL;
860     if ( getServer( s->getUDN() ) != 0 ) return false;
861
862     msg_Dbg( _p_sd, "Adding server '%s'",
863             s->getFriendlyName() );
864
865     services_discovery_t* p_sd = _p_sd;
866
867     p_input_item = input_item_New( p_sd, "vlc://nop", s->getFriendlyName() );
868     s->setInputItem( p_input_item );
869
870     services_discovery_AddItem( p_sd, p_input_item, NULL );
871
872     _list.push_back( s );
873
874     return true;
875 }
876
877 MediaServer* MediaServerList::getServer( const char* psz_udn )
878 {
879     MediaServer* result = 0;
880
881     for ( unsigned int i = 0; i < _list.size(); i++ )
882     {
883         if( strcmp( psz_udn, _list[i]->getUDN() ) == 0 )
884         {
885             result = _list[i];
886             break;
887         }
888     }
889
890     return result;
891 }
892
893 MediaServer* MediaServerList::getServerBySID( const char* sid )
894 {
895     MediaServer* p_server = 0;
896
897     for ( unsigned int i = 0; i < _list.size(); i++ )
898     {
899         if ( _list[i]->compareSID( sid ) )
900         {
901             p_server = _list[i];
902             break;
903         }
904     }
905
906     return p_server;
907 }
908
909 void MediaServerList::removeServer( const char* psz_udn )
910 {
911     MediaServer* p_server = getServer( psz_udn );
912     if ( !p_server ) return;
913
914     msg_Dbg( _p_sd,
915             "Removing server '%s'", p_server->getFriendlyName() );
916
917     std::vector<MediaServer*>::iterator it;
918     for ( it = _list.begin(); it != _list.end(); it++ )
919     {
920         if ( *it == p_server )
921         {
922             _list.erase( it );
923             delete p_server;
924             break;
925         }
926     }
927 }
928
929
930 // Item...
931
932 Item::Item( Container* p_parent, const char* psz_object_id, const char* psz_title,
933            const char* psz_resource )
934 {
935     _parent = p_parent;
936
937     _objectID = psz_object_id;
938     _title = psz_title;
939     _resource = psz_resource;
940
941     _inputItem = NULL;
942 }
943
944 Item::~Item()
945 {
946     if(_inputItem)
947         vlc_gc_decref( _inputItem );
948 }
949
950 const char* Item::getObjectID() const
951 {
952     return _objectID.c_str();
953 }
954
955 const char* Item::getTitle() const
956 {
957     return _title.c_str();
958 }
959
960 const char* Item::getResource() const
961 {
962     return _resource.c_str();
963 }
964
965 void Item::setInputItem( input_item_t* p_input_item )
966 {
967     if(_inputItem == p_input_item)
968         return;
969
970     if(_inputItem)
971         vlc_gc_decref( _inputItem );
972
973     vlc_gc_incref( p_input_item );
974     _inputItem = p_input_item;
975 }
976
977 input_item_t* Item::getInputItem() const
978 {
979     return _inputItem;
980 }
981
982
983 // Container...
984
985 Container::Container( Container*  p_parent,
986                       const char* psz_object_id,
987                       const char* psz_title )
988 {
989     _parent = p_parent;
990
991     _objectID = psz_object_id;
992     _title = psz_title;
993
994     _inputItem = NULL;
995 }
996
997 Container::~Container()
998 {
999     for ( unsigned int i = 0; i < _containers.size(); i++ )
1000     {
1001         delete _containers[i];
1002     }
1003
1004     for ( unsigned int i = 0; i < _items.size(); i++ )
1005     {
1006         delete _items[i];
1007     }
1008
1009     if(_inputItem )
1010         vlc_gc_decref( _inputItem );
1011 }
1012
1013 void Container::addItem( Item* item )
1014 {
1015     _items.push_back( item );
1016 }
1017
1018 void Container::addContainer( Container* p_container )
1019 {
1020     _containers.push_back( p_container );
1021 }
1022
1023 const char* Container::getObjectID() const
1024 {
1025     return _objectID.c_str();
1026 }
1027
1028 const char* Container::getTitle() const
1029 {
1030     return _title.c_str();
1031 }
1032
1033 unsigned int Container::getNumItems() const
1034 {
1035     return _items.size();
1036 }
1037
1038 unsigned int Container::getNumContainers() const
1039 {
1040     return _containers.size();
1041 }
1042
1043 Item* Container::getItem( unsigned int i_index ) const
1044 {
1045     if ( i_index < _items.size() ) return _items[i_index];
1046     return 0;
1047 }
1048
1049 Container* Container::getContainer( unsigned int i_index ) const
1050 {
1051     if ( i_index < _containers.size() ) return _containers[i_index];
1052     return 0;
1053 }
1054
1055 Container* Container::getParent()
1056 {
1057     return _parent;
1058 }
1059
1060 void Container::setInputItem( input_item_t* p_input_item )
1061 {
1062     if(_inputItem == p_input_item)
1063         return;
1064
1065     if(_inputItem)
1066         vlc_gc_decref( _inputItem );
1067
1068     vlc_gc_incref( p_input_item );
1069     _inputItem = p_input_item;
1070 }
1071
1072 input_item_t* Container::getInputItem() const
1073 {
1074     return _inputItem;
1075 }