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