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