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