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