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