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