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