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