]> git.sesse.net Git - vlc/blob - modules/services_discovery/upnp_intel.cpp
upnp_cc: compile fix (tested this time)
[vlc] / modules / services_discovery / upnp_intel.cpp
1 /*****************************************************************************
2  * Upnp_intell.cpp :  UPnP discovery module (Intel SDK)
3  *****************************************************************************
4  * Copyright (C) 2004-2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: RĂ©mi Denis-Courmont <rem # videolan.org> (original plugin)
8  *          Christian Henz <henz # c-lab.de>
9  *
10  * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 /*
28   \TODO: Debug messages: "__FILE__, __LINE__" ok ???, Wrn/Err ???
29   \TODO: Change names to VLC standard ???
30 */
31
32 #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         playlist_NodeDelete( p_sys->p_playlist, p_sys->p_node_cat, VLC_TRUE,
316                              VLC_TRUE );
317         vlc_object_release( p_sys->p_playlist );
318     }
319
320     free( p_sys );
321 }
322
323 static void Run( services_discovery_t* p_sd )
324 {
325     int res;
326
327     res = UpnpInit( 0, 0 );
328     if( res != UPNP_E_SUCCESS )
329     {
330     msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
331     return;
332     }
333
334     Cookie cookie;
335     cookie.serviceDiscovery = p_sd;
336     cookie.serverList = new MediaServerList( &cookie );
337
338     CallbackLock = new Lockable( &cookie );
339
340     res = UpnpRegisterClient( Callback, &cookie, &cookie.clientHandle );
341     if( res != UPNP_E_SUCCESS )
342     {
343     msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
344     goto shutDown;
345     }
346
347     res = UpnpSearchAsync( cookie.clientHandle, 5, MEDIA_SERVER_DEVICE_TYPE, &cookie );
348     if( res != UPNP_E_SUCCESS )
349     {
350     msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
351     goto shutDown;
352     }
353
354     msg_Dbg( p_sd, "UPnP discovery started" );
355     while( !p_sd->b_die )
356     {
357     msleep( 500 );
358     }
359
360     msg_Dbg( p_sd, "UPnP discovery stopped" );
361
362  shutDown:
363     UpnpFinish();
364     delete cookie.serverList;
365     delete CallbackLock;
366 }
367
368
369 // XML utility functions:
370
371 // Returns the value of a child element, or 0 on error
372 const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName )
373 {
374     if ( !parent ) return 0;
375     if ( !tagName ) return 0;
376
377     char* s = strdup( tagName );
378     IXML_NodeList* nodeList = ixmlElement_getElementsByTagName( parent, s );
379     free( s );
380     if ( !nodeList ) return 0;
381
382     IXML_Node* element = ixmlNodeList_item( nodeList, 0 );
383     ixmlNodeList_free( nodeList );
384     if ( !element ) return 0;
385
386     IXML_Node* textNode = ixmlNode_getFirstChild( element );
387     if ( !textNode ) return 0;
388
389     return ixmlNode_getNodeValue( textNode );
390 }
391
392 // Extracts the result document from a SOAP response
393 IXML_Document* parseBrowseResult( IXML_Document* doc )
394 {
395     if ( !doc ) return 0;
396
397     IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc, "Result" );
398     if ( !resultList ) return 0;
399
400     IXML_Node* resultNode = ixmlNodeList_item( resultList, 0 );
401
402     ixmlNodeList_free( resultList );
403
404     if ( !resultNode ) return 0;
405
406     IXML_Node* textNode = ixmlNode_getFirstChild( resultNode );
407     if ( !textNode ) return 0;
408
409     const char* resultString = ixmlNode_getNodeValue( textNode );
410     char* resultXML = strdup( resultString );
411     
412     resolve_xml_special_chars( resultXML );
413
414     IXML_Document* browseDoc = ixmlParseBuffer( resultXML );
415
416     free( resultXML );
417
418     return browseDoc;
419 }
420
421
422 // Handles all UPnP events
423 static int Callback( Upnp_EventType eventType, void* event, void* pCookie )
424 {
425     Locker locker( CallbackLock );
426
427     Cookie* cookie = ( Cookie* )pCookie;
428
429     switch( eventType ) {
430
431     case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
432     case UPNP_DISCOVERY_SEARCH_RESULT:
433     {
434         struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
435
436         IXML_Document *descriptionDoc = 0;
437
438         int res;
439         res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc );
440         if ( res != UPNP_E_SUCCESS )
441         {
442           msg_Dbg( cookie->serviceDiscovery, "%s:%d: Could not download device description!", __FILE__, __LINE__ );
443           return res;
444         }
445
446         MediaServer::parseDeviceDescription( descriptionDoc, discovery->Location, cookie );
447
448         ixmlDocument_free( descriptionDoc );
449     }
450     break;
451
452     case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
453     {
454         struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
455
456         cookie->serverList->removeServer( discovery->DeviceId );
457     }
458     break;
459
460     case UPNP_EVENT_RECEIVED:
461     {
462         Upnp_Event* e = ( Upnp_Event* )event;
463
464         MediaServer* server = cookie->serverList->getServerBySID( e->Sid );
465         if ( server ) server->fetchContents();
466     }
467     break;
468
469     case UPNP_EVENT_AUTORENEWAL_FAILED:
470     case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
471     {
472         // Re-subscribe...
473
474         Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;
475
476         MediaServer* server = cookie->serverList->getServerBySID( s->Sid );
477         if ( server ) server->subscribeToContentDirectory();
478     }
479     break;
480
481     case UPNP_EVENT_SUBSCRIBE_COMPLETE:
482         msg_Warn( cookie->serviceDiscovery, "subscription complete" );
483         break;
484         
485     case UPNP_DISCOVERY_SEARCH_TIMEOUT:
486         msg_Warn( cookie->serviceDiscovery, "search timeout" );
487         break;
488         
489     default:
490     msg_Dbg( cookie->serviceDiscovery, "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )", __FILE__, __LINE__, eventType );
491     break;
492     }
493
494     return UPNP_E_SUCCESS;
495 }
496
497
498 // Class implementations...
499
500 // MediaServer...
501
502 void MediaServer::parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie )
503 {
504     if ( !doc ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; }
505     if ( !location ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; }
506
507     const char* baseURL = location;
508
509     // Try to extract baseURL
510
511     IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" );
512     if ( urlList )
513     {
514     if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) )
515     {
516         IXML_Node* textNode = ixmlNode_getFirstChild( urlNode );
517         if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode );
518     }
519
520     ixmlNodeList_free( urlList );
521     }
522
523     // Get devices
524
525     IXML_NodeList* deviceList = ixmlDocument_getElementsByTagName( doc, "device" );
526     if ( deviceList )
527     {
528
529     for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ )
530     {
531         IXML_Element* deviceElement = ( IXML_Element* )ixmlNodeList_item( deviceList, i );
532
533         const char* deviceType = xml_getChildElementValue( deviceElement, "deviceType" );
534         if ( !deviceType ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no deviceType!", __FILE__, __LINE__ ); continue; }
535         if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 ) continue;
536
537         const char* UDN = xml_getChildElementValue( deviceElement, "UDN" );
538         if ( !UDN ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no UDN!", __FILE__, __LINE__ ); continue; }
539         if ( cookie->serverList->getServer( UDN ) != 0 ) continue;
540
541         const char* friendlyName = xml_getChildElementValue( deviceElement, "friendlyName" );
542         if ( !friendlyName ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no friendlyName!", __FILE__, __LINE__ ); continue; }
543
544         MediaServer* server = new MediaServer( UDN, friendlyName, cookie );
545         if ( !cookie->serverList->addServer( server ) ) {
546
547         delete server;
548         server = 0;
549         continue;
550         }
551
552         // Check for ContentDirectory service...
553
554         IXML_NodeList* serviceList = ixmlElement_getElementsByTagName( deviceElement, "service" );
555         if ( serviceList )
556         {
557             for ( unsigned int j = 0; j < ixmlNodeList_length( serviceList ); j++ )
558         {
559             IXML_Element* serviceElement = ( IXML_Element* )ixmlNodeList_item( serviceList, j );
560
561             const char* serviceType = xml_getChildElementValue( serviceElement, "serviceType" );
562             if ( !serviceType ) continue;
563             if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE, serviceType ) != 0 ) continue;
564
565             const char* eventSubURL = xml_getChildElementValue( serviceElement, "eventSubURL" );
566             if ( !eventSubURL ) continue;
567
568             const char* controlURL = xml_getChildElementValue( serviceElement, "controlURL" );
569             if ( !controlURL ) continue;
570
571             // Try to subscribe to ContentDirectory service
572
573             char* url = ( char* )malloc( strlen( baseURL ) + strlen( eventSubURL ) + 1 );
574             if ( url )
575             {
576                 char* s1 = strdup( baseURL );
577                 char* s2 = strdup( eventSubURL );
578
579                 if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS )
580                 {
581                 // msg_Dbg( cookie->serviceDiscovery, "CDS EVENT URL: %s", url );
582
583                 server->setContentDirectoryEventURL( url );
584                 server->subscribeToContentDirectory();
585                 }
586
587                 free( s1 );
588                 free( s2 );
589                 free( url );
590             }
591
592             // Try to browse content directory...
593
594             url = ( char* )malloc( strlen( baseURL ) + strlen( controlURL ) + 1 );
595             if ( url )
596             {
597             char* s1 = strdup( baseURL );
598             char* s2 = strdup( controlURL );
599
600             if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS )
601             {
602                 // msg_Dbg( cookie->serviceDiscovery, "CDS CTRL URL: %s", url );
603
604                 server->setContentDirectoryControlURL( url );
605                 server->fetchContents();
606             }
607
608             free( s1 );
609             free( s2 );
610             free( url );
611             }
612         }
613
614         ixmlNodeList_free( serviceList );
615         }
616     }
617
618     ixmlNodeList_free( deviceList );
619     }
620 }
621
622 MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie )
623 {
624     _cookie = cookie;
625
626     _UDN = UDN;
627     _friendlyName = friendlyName;
628
629     _contents = 0;
630     _playlistNode = 0;
631 }
632
633 MediaServer::~MediaServer()
634 {
635     if ( _contents )
636     {
637     playlist_NodeDelete( _cookie->serviceDiscovery->p_sys->p_playlist,
638                 _playlistNode,
639                 true,
640                 true );
641     }
642
643     delete _contents;
644 }
645
646 const char* MediaServer::getUDN() const
647 {
648   const char* s = _UDN.c_str();
649   return s;
650 }
651
652 const char* MediaServer::getFriendlyName() const
653 {
654     const char* s = _friendlyName.c_str();
655     return s;
656 }
657
658 void MediaServer::setContentDirectoryEventURL( const char* url )
659 {
660     _contentDirectoryEventURL = url;
661 }
662
663 const char* MediaServer::getContentDirectoryEventURL() const
664 {
665     const char* s =  _contentDirectoryEventURL.c_str();
666     return s;
667 }
668
669 void MediaServer::setContentDirectoryControlURL( const char* url )
670 {
671     _contentDirectoryControlURL = url;
672 }
673
674 const char* MediaServer::getContentDirectoryControlURL() const
675 {
676     return _contentDirectoryControlURL.c_str();
677 }
678
679 void MediaServer::subscribeToContentDirectory()
680 {
681     const char* url = getContentDirectoryEventURL();
682     if ( !url || strcmp( url, "" ) == 0 )
683     {
684     msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" );
685     return;
686     }
687
688     int timeOut = 1810;
689     Upnp_SID sid;
690
691     int res = UpnpSubscribe( _cookie->clientHandle, url, &timeOut, sid );
692
693     if ( res == UPNP_E_SUCCESS )
694     {
695     _subscriptionTimeOut = timeOut;
696     memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
697     }
698     else
699     {
700     msg_Dbg( _cookie->serviceDiscovery, "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__, getFriendlyName(), UpnpGetErrorMessage( res ) );
701     }
702 }
703
704 IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pBrowseFlag, const char* pFilter,
705                        const char* pStartingIndex, const char* pRequestedCount, const char* pSortCriteria )
706 {
707     IXML_Document* action = 0;
708     IXML_Document* response = 0;
709
710     const char* url = getContentDirectoryControlURL();
711     if ( !url || strcmp( url, "" ) == 0 ) { msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" ); return 0; }
712
713     char* ObjectID = strdup( pObjectID );
714     char* BrowseFlag = strdup( pBrowseFlag );
715     char* Filter = strdup( pFilter );
716     char* StartingIndex = strdup( pStartingIndex );
717     char* RequestedCount = strdup( pRequestedCount );
718     char* SortCriteria = strdup( pSortCriteria );
719
720     char* serviceType = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
721
722     int res;
723
724     res = UpnpAddToAction( &action, "Browse", serviceType, "ObjectID", ObjectID );
725     if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
726
727     res = UpnpAddToAction( &action, "Browse", serviceType, "BrowseFlag", BrowseFlag );
728     if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
729
730     res = UpnpAddToAction( &action, "Browse", serviceType, "Filter", Filter );
731     if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
732
733     res = UpnpAddToAction( &action, "Browse", serviceType, "StartingIndex", StartingIndex );
734     if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
735
736     res = UpnpAddToAction( &action, "Browse", serviceType, "RequestedCount", RequestedCount );
737     if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
738
739     res = UpnpAddToAction( &action, "Browse", serviceType, "SortCriteria", SortCriteria );
740     if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
741
742     res = UpnpSendAction( _cookie->clientHandle,
743               url,
744               CONTENT_DIRECTORY_SERVICE_TYPE,
745               0,
746               action,
747               &response );
748     if ( res != UPNP_E_SUCCESS )
749     {
750     msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) );
751     ixmlDocument_free( response );
752     response = 0;
753     }
754
755  browseActionCleanup:
756
757     free( ObjectID );
758     free( BrowseFlag );
759     free( Filter );
760     free( StartingIndex );
761     free( RequestedCount );
762     free( SortCriteria );
763
764     free( serviceType );
765
766     ixmlDocument_free( action );
767     return response;
768 }
769
770 void MediaServer::fetchContents()
771 {
772     Container* root = new Container( 0, "0", getFriendlyName() );
773     _fetchContents( root );
774
775     if ( _contents )
776     {
777     vlc_mutex_lock( &_cookie->serviceDiscovery->p_sys->p_playlist->object_lock );
778
779     playlist_NodeEmpty( _cookie->serviceDiscovery->p_sys->p_playlist,
780                _playlistNode,
781                true );
782
783     vlc_mutex_unlock( &_cookie->serviceDiscovery->p_sys->p_playlist->object_lock );
784
785     delete _contents;
786     }
787
788     _contents = root;
789     _contents->setPlaylistNode( _playlistNode );
790
791     _buildPlaylist( _contents );
792 }
793
794 bool MediaServer::_fetchContents( Container* parent )
795 {
796     if (!parent) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: parent==NULL", __FILE__, __LINE__ ); return false; }
797
798     IXML_Document* response = _browseAction( parent->getObjectID(), "BrowseDirectChildren", "*", "0", "0", "" );
799     if ( !response ) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR!", __FILE__, __LINE__ ); return false; }
800
801     IXML_Document* result = parseBrowseResult( response );
802     ixmlDocument_free( response );
803     if ( !result ) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR!", __FILE__, __LINE__ ); return false; }
804
805     IXML_NodeList* containerNodeList = ixmlDocument_getElementsByTagName( result, "container" );
806     if ( containerNodeList )
807     {
808     for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ )
809     {
810               IXML_Element* containerElement = ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
811
812         const char* objectID = ixmlElement_getAttribute( containerElement, "id" );
813         if ( !objectID ) continue;
814
815         const char* childCountStr = ixmlElement_getAttribute( containerElement, "childCount" );
816         if ( !childCountStr ) continue;
817         int childCount = atoi( childCountStr );
818
819         const char* title = xml_getChildElementValue( containerElement, "dc:title" );
820         if ( !title ) continue;
821
822         const char* resource = xml_getChildElementValue( containerElement, "res" );
823
824         if ( resource && childCount < 1 )
825         {
826         Item* item = new Item( parent, objectID, title, resource );
827         parent->addItem( item );
828         }
829         else
830         {
831         Container* container = new Container( parent, objectID, title );
832         parent->addContainer( container );
833
834         if ( childCount > 0 ) _fetchContents( container );
835         }
836     }
837
838     ixmlNodeList_free( containerNodeList );
839     }
840
841     IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result, "item" );
842     if ( itemNodeList )
843     {
844         for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
845     {
846         IXML_Element* itemElement = ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
847
848         const char* objectID = ixmlElement_getAttribute( itemElement, "id" );
849         if ( !objectID ) continue;
850
851         const char* title = xml_getChildElementValue( itemElement, "dc:title" );
852         if ( !title ) continue;
853
854         const char* resource = xml_getChildElementValue( itemElement, "res" );
855         if ( !resource ) continue;
856
857         Item* item = new Item( parent, objectID, title, resource );
858         parent->addItem( item );
859     }
860
861     ixmlNodeList_free( itemNodeList );
862     }
863
864     ixmlDocument_free( result );
865
866     return true;
867 }
868
869 void MediaServer::_buildPlaylist( Container* parent )
870 {
871     for ( unsigned int i = 0; i < parent->getNumContainers(); i++ )
872     {
873     Container* container = parent->getContainer( i );
874     playlist_item_t* parentNode = parent->getPlaylistNode();
875
876     char* title = strdup( container->getTitle() );
877     playlist_item_t* node = playlist_NodeCreate( _cookie->serviceDiscovery->p_sys->p_playlist,
878                              title,
879                              parentNode );
880     free( title );
881
882     container->setPlaylistNode( node );
883     _buildPlaylist( container );
884     }
885
886     for ( unsigned int i = 0; i < parent->getNumItems(); i++ )
887     {
888     Item* item = parent->getItem( i );
889     playlist_item_t* parentNode = parent->getPlaylistNode();
890
891     playlist_item_t* node = playlist_ItemNew( _cookie->serviceDiscovery,
892                          item->getResource(),
893                          item->getTitle() );
894
895     playlist_NodeAddItem( _cookie->serviceDiscovery->p_sys->p_playlist,
896                  node,
897                  parentNode, PLAYLIST_APPEND, PLAYLIST_END );
898
899     item->setPlaylistNode( node );
900     }
901 }
902
903 void MediaServer::setPlaylistNode( playlist_item_t* playlistNode )
904 {
905     _playlistNode = playlistNode;
906 }
907
908 bool MediaServer::compareSID( const char* sid )
909 {
910     return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 );
911 }
912
913
914 // MediaServerList...
915
916 MediaServerList::MediaServerList( Cookie* cookie )
917 {
918     _cookie = cookie;
919 }
920
921 MediaServerList::~MediaServerList()
922 {
923     for ( unsigned int i = 0; i < _list.size(); i++ )
924     {
925     delete _list[i];
926     }
927 }
928
929 bool MediaServerList::addServer( MediaServer* s )
930 {
931     if ( getServer( s->getUDN() ) != 0 ) return false;
932
933     msg_Dbg( _cookie->serviceDiscovery, "Adding server '%s'", s->getFriendlyName() );
934
935     _list.push_back( s );
936
937     char* name = strdup( s->getFriendlyName() );
938     playlist_item_t* node = playlist_NodeCreate( _cookie->serviceDiscovery->p_sys->p_playlist,
939                         name,
940                         _cookie->serviceDiscovery->p_sys->p_node );
941     free( name );
942     s->setPlaylistNode( node );
943
944     return true;
945 }
946
947 MediaServer* MediaServerList::getServer( const char* UDN )
948 {
949     MediaServer* result = 0;
950
951     for ( unsigned int i = 0; i < _list.size(); i++ )
952     {
953         if( strcmp( UDN, _list[i]->getUDN() ) == 0 )
954     {
955         result = _list[i];
956         break;
957     }
958     }
959
960     return result;
961 }
962
963 MediaServer* MediaServerList::getServerBySID( const char* sid )
964 {
965     MediaServer* server = 0;
966
967     for ( unsigned int i = 0; i < _list.size(); i++ )
968     {
969     if ( _list[i]->compareSID( sid ) )
970     {
971         server = _list[i];
972         break;
973     }
974     }
975
976     return server;
977 }
978
979 void MediaServerList::removeServer( const char* UDN )
980 {
981     MediaServer* server = getServer( UDN );
982     if ( !server ) return;
983
984     msg_Dbg( _cookie->serviceDiscovery, "Removing server '%s'", server->getFriendlyName() );
985
986     std::vector<MediaServer*>::iterator it;
987     for ( it = _list.begin(); it != _list.end(); it++ )
988     {
989         if ( *it == server )
990     {
991               _list.erase( it );
992         delete server;
993         break;
994     }
995     }
996 }
997
998
999 // Item...
1000
1001 Item::Item( Container* parent, const char* objectID, const char* title, const char* resource )
1002 {
1003     _parent = parent;
1004
1005     _objectID = objectID;
1006     _title = title;
1007     _resource = resource;
1008
1009     _playlistNode = 0;
1010 }
1011
1012 const char* Item::getObjectID() const
1013 {
1014     return _objectID.c_str();
1015 }
1016
1017 const char* Item::getTitle() const
1018 {
1019     return _title.c_str();
1020 }
1021
1022 const char* Item::getResource() const
1023 {
1024     return _resource.c_str();
1025 }
1026
1027 void Item::setPlaylistNode( playlist_item_t* node )
1028 {
1029     _playlistNode = node;
1030 }
1031
1032 playlist_item_t* Item::getPlaylistNode() const
1033 {
1034     return _playlistNode;
1035 }
1036
1037
1038 // Container...
1039
1040 Container::Container( Container* parent, const char* objectID, const char* title )
1041 {
1042     _parent = parent;
1043
1044     _objectID = objectID;
1045     _title = title;
1046
1047     _playlistNode = 0;
1048 }
1049
1050 Container::~Container()
1051 {
1052     for ( unsigned int i = 0; i < _containers.size(); i++ )
1053     {
1054     delete _containers[i];
1055     }
1056
1057     for ( unsigned int i = 0; i < _items.size(); i++ )
1058     {
1059     delete _items[i];
1060     }
1061 }
1062
1063 void Container::addItem( Item* item )
1064 {
1065     _items.push_back( item );
1066 }
1067
1068 void Container::addContainer( Container* container )
1069 {
1070     _containers.push_back( container );
1071 }
1072
1073 const char* Container::getObjectID() const
1074 {
1075     return _objectID.c_str();
1076 }
1077
1078 const char* Container::getTitle() const
1079 {
1080     return _title.c_str();
1081 }
1082
1083 unsigned int Container::getNumItems() const
1084 {
1085     return _items.size();
1086 }
1087
1088 unsigned int Container::getNumContainers() const
1089 {
1090     return _containers.size();
1091 }
1092
1093 Item* Container::getItem( unsigned int i ) const
1094 {
1095     if ( i < _items.size() ) return _items[i];
1096     return 0;
1097 }
1098
1099 Container* Container::getContainer( unsigned int i ) const
1100 {
1101     if ( i < _containers.size() ) return _containers[i];
1102     return 0;
1103 }
1104
1105 void Container::setPlaylistNode( playlist_item_t* node )
1106 {
1107     _playlistNode = node;
1108 }
1109
1110 playlist_item_t* Container::getPlaylistNode() const
1111 {
1112     return _playlistNode;
1113 }