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