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