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