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