]> git.sesse.net Git - vlc/blob - modules/services_discovery/upnp_intel.cpp
upnp_intel: split the big cpp file in two, declaration and implementation.
[vlc] / modules / services_discovery / upnp_intel.cpp
1 /*****************************************************************************
2  * Upnp_intel.cpp :  UPnP discovery module (Intel SDK)
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   \TODO: Change names to VLC standard ???
31 */
32 #undef PACKAGE_NAME
33 #ifdef HAVE_CONFIG_H
34 # include "config.h"
35 #endif
36
37 #include "upnp_intel.hpp"
38
39 #include <vlc_plugin.h>
40 #include <vlc_services_discovery.h>
41
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 clientHandle;
51     MediaServerList* serverList;
52     Lockable* callbackLock;
53 };
54
55 // VLC callback prototypes
56 static int Open( vlc_object_t* );
57 static void Close( vlc_object_t* );
58
59 // Module descriptor
60
61 vlc_module_begin();
62     set_shortname( "UPnP" );
63     set_description( N_( "Universal Plug'n'Play discovery" ) );
64     set_category( CAT_PLAYLIST );
65     set_subcategory( SUBCAT_PLAYLIST_SD );
66     set_capability( "services_discovery", 0 );
67     set_callbacks( Open, Close );
68 vlc_module_end();
69
70
71 // More prototypes...
72
73 static int Callback( Upnp_EventType eventType, void* event, void* user_data );
74
75 const char* xml_getChildElementValue( IXML_Element* parent,
76                                       const char*   tagName );
77
78 IXML_Document* parseBrowseResult( IXML_Document* doc );
79
80
81 // VLC callbacks...
82
83 static int Open( vlc_object_t *p_this )
84 {
85     int 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     services_discovery_SetLocalizedName( p_sd, _("UPnP devices") );
94
95     res = UpnpInit( 0, 0 );
96     if( res != UPNP_E_SUCCESS )
97     {
98         msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
99         return VLC_EGENERIC;
100     }
101
102     p_sys->serverList = new MediaServerList( p_sd );
103     p_sys->callbackLock = new Lockable();
104
105     res = UpnpRegisterClient( Callback, p_sd, &p_sys->clientHandle );
106     if( res != UPNP_E_SUCCESS )
107     {
108         msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
109         Close( (vlc_object_t*) p_sd );
110         return VLC_EGENERIC;
111     }
112
113     res = UpnpSearchAsync( p_sys->clientHandle, 5,
114             MEDIA_SERVER_DEVICE_TYPE, p_sd );
115     
116     if( res != UPNP_E_SUCCESS )
117     {
118         msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
119         Close( (vlc_object_t*) p_sd );
120         return VLC_EGENERIC;
121     }
122
123     return VLC_SUCCESS;
124 }
125
126 static void Close( vlc_object_t *p_this )
127 {
128     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
129
130     UpnpFinish();
131     delete p_sd->p_sys->serverList;
132     delete p_sd->p_sys->callbackLock;
133
134     free( p_sd->p_sys );
135 }
136
137 // XML utility functions:
138
139 // Returns the value of a child element, or 0 on error
140 const char* xml_getChildElementValue( IXML_Element* parent,
141                                       const char*   tagName )
142 {
143     if ( !parent ) return 0;
144     if ( !tagName ) return 0;
145
146     char* s = strdup( tagName );
147     IXML_NodeList* nodeList = ixmlElement_getElementsByTagName( parent, s );
148     free( s );
149     if ( !nodeList ) return 0;
150
151     IXML_Node* element = ixmlNodeList_item( nodeList, 0 );
152     ixmlNodeList_free( nodeList );
153     if ( !element ) return 0;
154
155     IXML_Node* textNode = ixmlNode_getFirstChild( element );
156     if ( !textNode ) return 0;
157
158     return ixmlNode_getNodeValue( textNode );
159 }
160
161 // Extracts the result document from a SOAP response
162 IXML_Document* parseBrowseResult( IXML_Document* doc )
163 {
164     ixmlRelaxParser(1);
165
166     if ( !doc ) return 0;
167
168     IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc,
169                                                                    "Result" );
170
171     if ( !resultList ) return 0;
172
173     IXML_Node* resultNode = ixmlNodeList_item( resultList, 0 );
174
175     ixmlNodeList_free( resultList );
176
177     if ( !resultNode ) return 0;
178
179     IXML_Node* textNode = ixmlNode_getFirstChild( resultNode );
180     if ( !textNode ) return 0;
181
182     const char* resultString = ixmlNode_getNodeValue( textNode );
183     char* resultXML = strdup( resultString );
184
185     IXML_Document* browseDoc = ixmlParseBuffer( resultXML );
186
187     free( resultXML );
188
189     return browseDoc;
190 }
191
192
193 // Handles all UPnP events
194 static int Callback( Upnp_EventType eventType, void* event, void* user_data )
195 {
196     services_discovery_t *p_sd = ( services_discovery_t* ) user_data;
197     services_discovery_sys_t* p_sys = p_sd->p_sys;
198     Locker locker( p_sys->callbackLock );
199
200     switch( eventType ) {
201
202     case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
203     case UPNP_DISCOVERY_SEARCH_RESULT:
204     {
205         struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
206
207         IXML_Document *descriptionDoc = 0;
208
209         int res;
210         res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc );
211         if ( res != UPNP_E_SUCCESS )
212         {
213             msg_Dbg( p_sd,
214                     "%s:%d: Could not download device description!",
215                     __FILE__, __LINE__ );
216             return res;
217         }
218
219         MediaServer::parseDeviceDescription( descriptionDoc,
220                 discovery->Location, p_sd );
221
222         ixmlDocument_free( descriptionDoc );
223     }
224     break;
225
226     case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
227     {
228         struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
229
230         p_sys->serverList->removeServer( discovery->DeviceId );
231     }
232     break;
233
234     case UPNP_EVENT_RECEIVED:
235     {
236         Upnp_Event* e = ( Upnp_Event* )event;
237
238         MediaServer* server = p_sys->serverList->getServerBySID( e->Sid );
239         if ( server ) server->fetchContents();
240     }
241     break;
242
243     case UPNP_EVENT_AUTORENEWAL_FAILED:
244     case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
245     {
246         // Re-subscribe...
247
248         Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;
249
250         MediaServer* server = p_sys->serverList->getServerBySID( s->Sid );
251         if ( server ) server->subscribeToContentDirectory();
252     }
253     break;
254
255     case UPNP_EVENT_SUBSCRIBE_COMPLETE:
256         msg_Warn( p_sd, "subscription complete" );
257         break;
258  
259     case UPNP_DISCOVERY_SEARCH_TIMEOUT:
260         msg_Warn( p_sd, "search timeout" );
261         break;
262  
263     default:
264     msg_Dbg( p_sd,
265             "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )",
266             __FILE__, __LINE__, eventType );
267     break;
268     }
269
270     return UPNP_E_SUCCESS;
271 }
272
273
274 // Class implementations...
275
276 // MediaServer...
277
278 void MediaServer::parseDeviceDescription( IXML_Document* doc,
279                                           const char*    location,
280                                           services_discovery_t* p_sd )
281 {
282     if ( !doc )
283     { 
284         msg_Dbg( p_sd, "%s:%d: NULL", __FILE__, __LINE__ ); 
285         return;
286     }
287     
288     if ( !location )
289     {
290         msg_Dbg( p_sd, "%s:%d: NULL", __FILE__, __LINE__ ); 
291         return;
292     }
293
294     const char* baseURL = location;
295
296     // Try to extract baseURL
297
298     IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" );
299     if ( !urlList )
300     {
301
302         if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) )
303         {
304             IXML_Node* textNode = ixmlNode_getFirstChild( urlNode );
305             if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode );
306         }
307
308         ixmlNodeList_free( urlList );
309     }
310
311     // Get devices
312
313     IXML_NodeList* deviceList =
314                 ixmlDocument_getElementsByTagName( doc, "device" );
315     
316     if ( deviceList )
317     {
318         for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ )
319         {
320             IXML_Element* deviceElement =
321                    ( IXML_Element* ) ixmlNodeList_item( deviceList, i );
322
323             const char* deviceType = xml_getChildElementValue( deviceElement,
324                                                                "deviceType" );
325             if ( !deviceType )
326             {
327                 msg_Dbg( p_sd,
328                         "%s:%d: no deviceType!",
329                         __FILE__, __LINE__ );
330                 continue;
331             }
332
333             if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 )
334                 continue;
335
336             const char* UDN = xml_getChildElementValue( deviceElement, "UDN" );
337             if ( !UDN )
338             {
339                 msg_Dbg( p_sd, "%s:%d: no UDN!",
340                         __FILE__, __LINE__ );
341                 continue;
342             }
343             
344             if ( p_sd->p_sys->serverList->getServer( UDN ) != 0 )
345                 continue;
346
347             const char* friendlyName =
348                        xml_getChildElementValue( deviceElement, 
349                                                  "friendlyName" );
350             
351             if ( !friendlyName )
352             {
353                 msg_Dbg( p_sd, "%s:%d: no friendlyName!", __FILE__, __LINE__ );
354                 continue;
355             }
356
357             MediaServer* server = new MediaServer( UDN, friendlyName, p_sd );
358             
359             if ( !p_sd->p_sys->serverList->addServer( server ) )
360             {
361
362                 delete server;
363                 server = 0;
364                 continue;
365             }
366
367             // Check for ContentDirectory service...
368             IXML_NodeList* serviceList =
369                        ixmlElement_getElementsByTagName( deviceElement,
370                                                          "service" );
371             if ( serviceList )
372             {
373                 for ( unsigned int j = 0;
374                       j < ixmlNodeList_length( serviceList ); j++ )
375                 {
376                     IXML_Element* serviceElement =
377                         ( IXML_Element* ) ixmlNodeList_item( serviceList, j );
378
379                     const char* serviceType =
380                         xml_getChildElementValue( serviceElement,
381                                                   "serviceType" );
382                     if ( !serviceType )
383                         continue;
384
385                     if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE,
386                                 serviceType ) != 0 )
387                         continue;
388
389                     const char* eventSubURL =
390                         xml_getChildElementValue( serviceElement,
391                                                   "eventSubURL" );
392                     if ( !eventSubURL )
393                         continue;
394
395                     const char* controlURL =
396                         xml_getChildElementValue( serviceElement,
397                                                   "controlURL" );
398                     if ( !controlURL )
399                         continue;
400
401                     // Try to subscribe to ContentDirectory service
402
403                     char* url = ( char* ) malloc( strlen( baseURL ) +
404                             strlen( eventSubURL ) + 1 );
405                     if ( url )
406                     {
407                         char* s1 = strdup( baseURL );
408                         char* s2 = strdup( eventSubURL );
409
410                         if ( UpnpResolveURL( s1, s2, url ) ==
411                                 UPNP_E_SUCCESS )
412                         {
413                             server->setContentDirectoryEventURL( url );
414                             server->subscribeToContentDirectory();
415                         }
416
417                         free( s1 );
418                         free( s2 );
419                         free( url );
420                     }
421
422                     // Try to browse content directory...
423
424                     url = ( char* ) malloc( strlen( baseURL ) +
425                             strlen( controlURL ) + 1 );
426                     if ( url )
427                     {
428                         char* s1 = strdup( baseURL );
429                         char* s2 = strdup( controlURL );
430
431                         if ( UpnpResolveURL( s1, s2, url ) ==
432                                 UPNP_E_SUCCESS )
433                         {
434                             server->setContentDirectoryControlURL( url );
435                             server->fetchContents();
436                         }
437
438                         free( s1 );
439                         free( s2 );
440                         free( url );
441                     }
442                }
443                ixmlNodeList_free( serviceList );
444            }
445        }
446        ixmlNodeList_free( deviceList );
447     }
448 }
449
450 MediaServer::MediaServer( const char* UDN,
451                           const char* friendlyName,
452                           services_discovery_t* p_sd )
453 {
454     _p_sd = p_sd;
455
456     _UDN = UDN;
457     _friendlyName = friendlyName;
458
459     _contents = NULL;
460     _inputItem = NULL;
461 }
462
463 MediaServer::~MediaServer()
464 {
465     delete _contents;
466 }
467
468 const char* MediaServer::getUDN() const
469 {
470   const char* s = _UDN.c_str();
471   return s;
472 }
473
474 const char* MediaServer::getFriendlyName() const
475 {
476     const char* s = _friendlyName.c_str();
477     return s;
478 }
479
480 void MediaServer::setContentDirectoryEventURL( const char* url )
481 {
482     _contentDirectoryEventURL = url;
483 }
484
485 const char* MediaServer::getContentDirectoryEventURL() const
486 {
487     const char* s =  _contentDirectoryEventURL.c_str();
488     return s;
489 }
490
491 void MediaServer::setContentDirectoryControlURL( const char* url )
492 {
493     _contentDirectoryControlURL = url;
494 }
495
496 const char* MediaServer::getContentDirectoryControlURL() const
497 {
498     return _contentDirectoryControlURL.c_str();
499 }
500
501 void MediaServer::subscribeToContentDirectory()
502 {
503     const char* url = getContentDirectoryEventURL();
504     if ( !url || strcmp( url, "" ) == 0 )
505     {
506         msg_Dbg( _p_sd, "No subscription url set!" );
507         return;
508     }
509
510     int timeOut = 1810;
511     Upnp_SID sid;
512
513     int res = UpnpSubscribe( _p_sd->p_sys->clientHandle, url, &timeOut, sid );
514
515     if ( res == UPNP_E_SUCCESS )
516     {
517         _subscriptionTimeOut = timeOut;
518         memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
519     }
520     else
521     {
522         msg_Dbg( _p_sd,
523                 "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__,
524                 getFriendlyName(), UpnpGetErrorMessage( res ) );
525     }
526 }
527
528 IXML_Document* MediaServer::_browseAction( const char* pObjectID,
529                                            const char* pBrowseFlag,
530                                            const char* pFilter,
531                                            const char* pStartingIndex,
532                                            const char* pRequestedCount,
533                                            const char* pSortCriteria )
534 {
535     IXML_Document* action = 0;
536     IXML_Document* response = 0;
537     const char* url = getContentDirectoryControlURL();
538     
539     if ( !url || strcmp( url, "" ) == 0 )
540     {
541         msg_Dbg( _p_sd, "No subscription url set!" );
542         return 0;
543     }
544
545     char* ObjectID = strdup( pObjectID );
546     char* BrowseFlag = strdup( pBrowseFlag );
547     char* Filter = strdup( pFilter );
548     char* StartingIndex = strdup( pStartingIndex );
549     char* RequestedCount = strdup( pRequestedCount );
550     char* SortCriteria = strdup( pSortCriteria );
551     char* serviceType = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
552
553     int res;
554
555     res = UpnpAddToAction( &action, "Browse",
556             serviceType, "ObjectID", ObjectID );
557     
558     if ( res != UPNP_E_SUCCESS ) 
559     {
560         msg_Dbg( _p_sd,
561                  "%s:%d: ERROR: %s", __FILE__, __LINE__,
562                  UpnpGetErrorMessage( res ) );
563         goto browseActionCleanup;
564     }
565
566     res = UpnpAddToAction( &action, "Browse",
567             serviceType, "BrowseFlag", BrowseFlag );
568     
569     if ( res != UPNP_E_SUCCESS )
570     {
571         msg_Dbg( _p_sd,
572              "%s:%d: ERROR: %s", __FILE__, __LINE__,
573              UpnpGetErrorMessage( res ) );
574         goto browseActionCleanup;
575     }
576
577     res = UpnpAddToAction( &action, "Browse",
578             serviceType, "Filter", Filter );
579     
580     if ( res != UPNP_E_SUCCESS )
581     {
582         msg_Dbg( _p_sd,
583              "%s:%d: ERROR: %s", __FILE__, __LINE__,
584              UpnpGetErrorMessage( res ) );
585         goto browseActionCleanup;
586     }
587
588     res = UpnpAddToAction( &action, "Browse",
589             serviceType, "StartingIndex", StartingIndex );
590
591     if ( res != UPNP_E_SUCCESS )
592     {
593         msg_Dbg( _p_sd,
594              "%s:%d: ERROR: %s", __FILE__, __LINE__,
595              UpnpGetErrorMessage( res ) );
596         goto browseActionCleanup;
597     }
598
599     res = UpnpAddToAction( &action, "Browse",
600             serviceType, "RequestedCount", RequestedCount );
601
602     if ( res != UPNP_E_SUCCESS )
603     {
604         msg_Dbg( _p_sd,
605                 "%s:%d: ERROR: %s", __FILE__, __LINE__,
606                 UpnpGetErrorMessage( res ) ); goto browseActionCleanup; }
607
608     res = UpnpAddToAction( &action, "Browse",
609             serviceType, "SortCriteria", SortCriteria );
610     
611     if ( res != UPNP_E_SUCCESS )
612     {
613         msg_Dbg( _p_sd,
614              "%s:%d: ERROR: %s", __FILE__, __LINE__,
615              UpnpGetErrorMessage( res ) );
616         goto browseActionCleanup;
617     }
618
619     res = UpnpSendAction( _p_sd->p_sys->clientHandle,
620               url,
621               CONTENT_DIRECTORY_SERVICE_TYPE,
622               0,
623               action,
624               &response );
625     
626     if ( res != UPNP_E_SUCCESS )
627     {
628         msg_Dbg( _p_sd,
629                 "%s:%d: ERROR: %s when trying the send() action with URL: %s",
630                 __FILE__, __LINE__,
631                 UpnpGetErrorMessage( res ), url );
632
633         ixmlDocument_free( response );
634         response = 0;
635     }
636
637  browseActionCleanup:
638
639     free( ObjectID );
640     free( BrowseFlag );
641     free( Filter );
642     free( StartingIndex );
643     free( RequestedCount );
644     free( SortCriteria );
645
646     free( serviceType );
647
648     ixmlDocument_free( action );
649     return response;
650 }
651
652 void MediaServer::fetchContents()
653 {
654     Container* root = new Container( 0, "0", getFriendlyName() );
655     _fetchContents( root );
656
657    // if ( _contents )
658    // {
659    //     PL_LOCK;
660    //     playlist_NodeEmpty( p_playlist, _playlistNode, true );
661    //     PL_UNLOCK;
662    //     delete _contents;
663    // }
664
665     _contents = root;
666     _contents->setInputItem( _inputItem );
667
668     _buildPlaylist( _contents );
669 }
670
671 bool MediaServer::_fetchContents( Container* parent )
672 {
673     if (!parent)
674     {
675         msg_Dbg( _p_sd,
676                 "%s:%d: parent==NULL", __FILE__, __LINE__ );
677         return false;
678     }
679
680     IXML_Document* response = _browseAction( parent->getObjectID(),
681                                       "BrowseDirectChildren",
682                                       "*", "0", "0", "" );
683     if ( !response )
684     {
685         msg_Dbg( _p_sd,
686                 "%s:%d: ERROR! No response from browse() action",
687                 __FILE__, __LINE__ );
688         return false;
689     }
690
691     IXML_Document* result = parseBrowseResult( response );
692     ixmlDocument_free( response );
693     
694     if ( !result )
695     {
696         msg_Dbg( _p_sd,
697                 "%s:%d: ERROR! browse() response parsing failed",
698                 __FILE__, __LINE__ );
699         return false;
700     }
701
702     IXML_NodeList* containerNodeList =
703                 ixmlDocument_getElementsByTagName( result, "container" );
704     
705     if ( containerNodeList )
706     {
707         for ( unsigned int i = 0;
708                 i < ixmlNodeList_length( containerNodeList ); i++ )
709         {
710             IXML_Element* containerElement =
711                   ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
712
713             const char* objectID = ixmlElement_getAttribute( containerElement,
714                                                              "id" );
715             if ( !objectID )
716                 continue;
717
718             const char* childCountStr =
719                     ixmlElement_getAttribute( containerElement, "childCount" );
720             
721             if ( !childCountStr )
722                 continue;
723             
724             int childCount = atoi( childCountStr );
725             const char* title = xml_getChildElementValue( containerElement,
726                                                           "dc:title" );
727             
728             if ( !title )
729                 continue;
730             
731             const char* resource = xml_getChildElementValue( containerElement,
732                                                              "res" );
733
734             if ( resource && childCount < 1 )
735             {
736                 Item* item = new Item( parent, objectID, title, resource );
737                 parent->addItem( item );
738             }
739
740             else
741             {
742                 Container* container = new Container( parent, objectID, title );
743                 parent->addContainer( container );
744
745                 if ( childCount > 0 )
746                     _fetchContents( container );
747             }
748         }
749         ixmlNodeList_free( containerNodeList );
750     }
751
752     IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result,
753                                                                      "item" );
754     if ( itemNodeList )
755     {
756         for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
757         {
758             IXML_Element* itemElement =
759                         ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
760
761             const char* objectID =
762                         ixmlElement_getAttribute( itemElement, "id" );
763             
764             if ( !objectID )
765                 continue;
766
767             const char* title =
768                         xml_getChildElementValue( itemElement, "dc:title" );
769             
770             if ( !title )
771                 continue;
772
773             const char* resource =
774                         xml_getChildElementValue( itemElement, "res" );
775             
776             if ( !resource )
777                 continue;
778
779             Item* item = new Item( parent, objectID, title, resource );
780             parent->addItem( item );
781         }
782         ixmlNodeList_free( itemNodeList );
783     }
784
785     ixmlDocument_free( result );
786     return true;
787 }
788
789 void MediaServer::_buildPlaylist( Container* parent )
790 {
791     for ( unsigned int i = 0; i < parent->getNumContainers(); i++ )
792     {
793         Container* container = parent->getContainer( i );
794
795         input_item_t* p_input_item = input_item_New( _p_sd, "vlc://nop", parent->getTitle() ); 
796         input_item_AddSubItem( parent->getInputItem(), p_input_item );
797
798         container->setInputItem( p_input_item );
799         _buildPlaylist( container );
800     }
801
802     for ( unsigned int i = 0; i < parent->getNumItems(); i++ )
803     {
804         Item* item = parent->getItem( i );
805
806         input_item_t* p_input_item = input_item_New( _p_sd,
807                                                item->getResource(),
808                                                item->getTitle() );
809         assert( p_input_item );
810         input_item_AddSubItem( parent->getInputItem(), p_input_item );
811         item->setInputItem( p_input_item );
812     }
813 }
814
815 void MediaServer::setInputItem( input_item_t* p_input_item )
816 {
817     if(_inputItem == p_input_item)
818         return;
819
820     if(_inputItem)
821         vlc_gc_decref( _inputItem );
822
823     vlc_gc_incref( p_input_item );
824     _inputItem = p_input_item;
825 }
826
827 bool MediaServer::compareSID( const char* sid )
828 {
829     return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 );
830 }
831
832
833 // MediaServerList...
834
835 MediaServerList::MediaServerList( services_discovery_t* p_sd )
836 {
837     _p_sd = p_sd;
838 }
839
840 MediaServerList::~MediaServerList()
841 {
842     for ( unsigned int i = 0; i < _list.size(); i++ )
843     {
844         delete _list[i];
845     }
846 }
847
848 bool MediaServerList::addServer( MediaServer* s )
849 {
850     input_item_t* p_input_item = NULL;
851     if ( getServer( s->getUDN() ) != 0 ) return false;
852
853     msg_Dbg( _p_sd, "Adding server '%s'",
854             s->getFriendlyName() );
855
856     services_discovery_t* p_sd = _p_sd;
857
858     p_input_item = input_item_New( p_sd, "vlc://nop", s->getFriendlyName() ); 
859     s->setInputItem( p_input_item );
860
861     services_discovery_AddItem( p_sd, p_input_item, NULL );
862
863     _list.push_back( s );
864
865     return true;
866 }
867
868 MediaServer* MediaServerList::getServer( const char* UDN )
869 {
870     MediaServer* result = 0;
871
872     for ( unsigned int i = 0; i < _list.size(); i++ )
873     {
874         if( strcmp( UDN, _list[i]->getUDN() ) == 0 )
875         {
876             result = _list[i];
877             break;
878         }
879     }
880
881     return result;
882 }
883
884 MediaServer* MediaServerList::getServerBySID( const char* sid )
885 {
886     MediaServer* server = 0;
887
888     for ( unsigned int i = 0; i < _list.size(); i++ )
889     {
890         if ( _list[i]->compareSID( sid ) )
891         {
892             server = _list[i];
893             break;
894         }
895     }
896
897     return server;
898 }
899
900 void MediaServerList::removeServer( const char* UDN )
901 {
902     MediaServer* server = getServer( UDN );
903     if ( !server ) return;
904
905     msg_Dbg( _p_sd,
906             "Removing server '%s'", server->getFriendlyName() );
907
908     std::vector<MediaServer*>::iterator it;
909     for ( it = _list.begin(); it != _list.end(); it++ )
910     {
911         if ( *it == server )
912         {
913             _list.erase( it );
914             delete server;
915             break;
916         }
917     }
918 }
919
920
921 // Item...
922
923 Item::Item( Container* parent, const char* objectID, const char* title, const char* resource )
924 {
925     _parent = parent;
926
927     _objectID = objectID;
928     _title = title;
929     _resource = resource;
930
931     _inputItem = NULL;
932 }
933
934 Item::~Item()
935 {
936     if(_inputItem)
937         vlc_gc_decref( _inputItem );
938 }
939
940 const char* Item::getObjectID() const
941 {
942     return _objectID.c_str();
943 }
944
945 const char* Item::getTitle() const
946 {
947     return _title.c_str();
948 }
949
950 const char* Item::getResource() const
951 {
952     return _resource.c_str();
953 }
954
955 void Item::setInputItem( input_item_t* p_input_item )
956 {
957     if(_inputItem == p_input_item)
958         return;
959
960     if(_inputItem)
961         vlc_gc_decref( _inputItem );
962
963     vlc_gc_incref( p_input_item );
964     _inputItem = p_input_item;
965 }
966
967 input_item_t* Item::getInputItem() const
968 {
969     return _inputItem;
970 }
971
972
973 // Container...
974
975 Container::Container( Container*  parent,
976                       const char* objectID,
977                       const char* title )
978 {
979     _parent = parent;
980
981     _objectID = objectID;
982     _title = title;
983
984     _inputItem = NULL;
985 }
986
987 Container::~Container()
988 {
989     for ( unsigned int i = 0; i < _containers.size(); i++ )
990     {
991         delete _containers[i];
992     }
993
994     for ( unsigned int i = 0; i < _items.size(); i++ )
995     {
996         delete _items[i];
997     }
998
999     if(_inputItem )
1000         vlc_gc_decref( _inputItem );
1001 }
1002
1003 void Container::addItem( Item* item )
1004 {
1005     _items.push_back( item );
1006 }
1007
1008 void Container::addContainer( Container* container )
1009 {
1010     _containers.push_back( container );
1011 }
1012
1013 const char* Container::getObjectID() const
1014 {
1015     return _objectID.c_str();
1016 }
1017
1018 const char* Container::getTitle() const
1019 {
1020     return _title.c_str();
1021 }
1022
1023 unsigned int Container::getNumItems() const
1024 {
1025     return _items.size();
1026 }
1027
1028 unsigned int Container::getNumContainers() const
1029 {
1030     return _containers.size();
1031 }
1032
1033 Item* Container::getItem( unsigned int i ) const
1034 {
1035     if ( i < _items.size() ) return _items[i];
1036     return 0;
1037 }
1038
1039 Container* Container::getContainer( unsigned int i ) const
1040 {
1041     if ( i < _containers.size() ) return _containers[i];
1042     return 0;
1043 }
1044
1045 Container* Container::getParent()
1046 {
1047     return _parent;
1048 }
1049
1050 void Container::setInputItem( input_item_t* p_input_item )
1051 {
1052     if(_inputItem == p_input_item)
1053         return;
1054
1055     if(_inputItem)
1056         vlc_gc_decref( _inputItem );
1057
1058     vlc_gc_incref( p_input_item );
1059     _inputItem = p_input_item;
1060 }
1061
1062 input_item_t* Container::getInputItem() const
1063 {
1064     return _inputItem;
1065 }