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