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