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