]> git.sesse.net Git - vlc/blob - modules/services_discovery/upnp_intel.cpp
Use pl_Locked and pl_Unlocked
[vlc] / modules / services_discovery / upnp_intel.cpp
1 /*****************************************************************************
2  * Upnp_intell.cpp :  UPnP discovery module (Intel SDK)
3  *****************************************************************************
4  * Copyright (C) 2004-2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: RĂ©mi Denis-Courmont <rem # videolan.org> (original plugin)
8  *          Christian Henz <henz # c-lab.de>
9  *
10  * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 /*
28   \TODO: Debug messages: "__FILE__, __LINE__" ok ???, Wrn/Err ???
29   \TODO: Change names to VLC standard ???
30   \TODO: Rewrite this using the new service discovery API (see sap.c, shout.c).
31 */
32
33
34 #include <vector>
35 #include <string>
36
37 #include <upnp/upnp.h>
38 #include <upnp/upnptools.h>
39
40 #undef PACKAGE_NAME
41 #ifdef HAVE_CONFIG_H
42 # include "config.h"
43 #endif
44
45 #include <vlc_common.h>
46 #include <vlc_plugin.h>
47 #include <vlc_playlist.h>
48
49
50 // VLC handle
51
52 struct services_discovery_sys_t
53 {
54     playlist_t *p_playlist;
55     playlist_item_t *p_node_cat;
56     playlist_item_t *p_node_one;
57 };
58
59
60 // Constants
61
62 const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1";
63 const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1";
64
65
66 // Classes
67
68 class MediaServer;
69 class MediaServerList;
70 class Item;
71 class Container;
72
73 // Cookie that is passed to the callback
74
75 typedef struct
76 {
77     services_discovery_t* serviceDiscovery;
78     UpnpClient_Handle clientHandle;
79     MediaServerList* serverList;
80 } Cookie;
81
82
83 // Class definitions...
84
85 class Lockable
86 {
87 public:
88
89     Lockable( Cookie* c )
90     {
91     vlc_mutex_init( &_mutex );
92     }
93
94     ~Lockable()
95     {
96     vlc_mutex_destroy( &_mutex );
97     }
98
99     void lock() { vlc_mutex_lock( &_mutex ); }
100     void unlock() { vlc_mutex_unlock( &_mutex ); }
101
102 private:
103
104     vlc_mutex_t _mutex;
105 };
106
107
108 class Locker
109 {
110 public:
111     Locker( Lockable* l )
112     {
113     _lockable = l;
114     _lockable->lock();
115     }
116
117     ~Locker()
118     {
119     _lockable->unlock();
120     }
121
122 private:
123     Lockable* _lockable;
124 };
125
126
127 class MediaServer
128 {
129 public:
130
131     static void parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie );
132
133     MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie );
134     ~MediaServer();
135
136     const char* getUDN() const;
137     const char* getFriendlyName() const;
138
139     void setContentDirectoryEventURL( const char* url );
140     const char* getContentDirectoryEventURL() const;
141
142     void setContentDirectoryControlURL( const char* url );
143     const char* getContentDirectoryControlURL() const;
144
145     void subscribeToContentDirectory();
146     void fetchContents();
147
148     void setPlaylistNode( playlist_item_t* node );
149
150     bool compareSID( const char* sid );
151
152 private:
153
154     bool _fetchContents( Container* parent );
155     void _buildPlaylist( Container* container );
156     IXML_Document* _browseAction( const char*, const char*, const char*, const char*, const char*, const char* );
157
158     Cookie* _cookie;
159
160     Container* _contents;
161     playlist_item_t* _playlistNode;
162
163     std::string _UDN;
164     std::string _friendlyName;
165
166     std::string _contentDirectoryEventURL;
167     std::string _contentDirectoryControlURL;
168
169     int _subscriptionTimeOut;
170     Upnp_SID _subscriptionID;
171 };
172
173
174 class MediaServerList
175 {
176 public:
177
178     MediaServerList( Cookie* cookie );
179     ~MediaServerList();
180
181     bool addServer( MediaServer* s );
182     void removeServer( const char* UDN );
183
184     MediaServer* getServer( const char* UDN );
185     MediaServer* getServerBySID( const char* );
186
187 private:
188
189     Cookie* _cookie;
190
191     std::vector<MediaServer*> _list;
192 };
193
194
195 class Item
196 {
197 public:
198
199     Item( Container* parent, const char* objectID, const char* title, const char* resource );
200
201     const char* getObjectID() const;
202     const char* getTitle() const;
203     const char* getResource() const;
204
205     void setPlaylistNode( playlist_item_t* node );
206     playlist_item_t* getPlaylistNode() const ;
207
208 private:
209
210     playlist_item_t* _playlistNode;
211
212     Container* _parent;
213     std::string _objectID;
214     std::string _title;
215     std::string _resource;
216 };
217
218
219 class Container
220 {
221 public:
222
223     Container( Container* parent, const char* objectID, const char* title );
224     ~Container();
225
226     void addItem( Item* item );
227     void addContainer( Container* container );
228
229     const char* getObjectID() const;
230     const char* getTitle() const;
231
232     unsigned int getNumItems() const;
233     unsigned int getNumContainers() const;
234
235     Item* getItem( unsigned int i ) const;
236     Container* getContainer( unsigned int i ) const;
237
238     void setPlaylistNode( playlist_item_t* node );
239     playlist_item_t* getPlaylistNode() const;
240
241 private:
242
243     playlist_item_t* _playlistNode;
244
245     Container* _parent;
246
247     std::string _objectID;
248     std::string _title;
249     std::vector<Item*> _items;
250     std::vector<Container*> _containers;
251 };
252
253
254 // VLC callback prototypes
255
256 static int Open( vlc_object_t* );
257 static void Close( vlc_object_t* );
258 static void Run( services_discovery_t *p_sd );
259 static playlist_t *pl_Get( services_discovery_t *p_sd )
260 {
261     return p_sd->p_sys->p_playlist;
262 }
263
264 // Module descriptor
265
266 vlc_module_begin();
267 set_shortname( "UPnP" );
268 set_description( N_( "Universal Plug'n'Play discovery ( Intel SDK )" ) );
269 set_category( CAT_PLAYLIST );
270 set_subcategory( SUBCAT_PLAYLIST_SD );
271 set_capability( "services_discovery", 0 );
272 set_callbacks( Open, Close );
273 vlc_module_end();
274
275
276 // More prototypes...
277
278 static Lockable* CallbackLock;
279 static int Callback( Upnp_EventType eventType, void* event, void* pCookie );
280
281 const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName );
282 IXML_Document* parseBrowseResult( IXML_Document* doc );
283
284
285 // VLC callbacks...
286
287 static int Open( vlc_object_t *p_this )
288 {
289     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
290     services_discovery_sys_t *p_sys  = ( services_discovery_sys_t * )
291     malloc( sizeof( services_discovery_sys_t ) );
292
293     p_sd->pf_run = Run;
294     p_sd->p_sys = p_sys;
295     p_sys->p_playlist = pl_Yield( p_sd );
296
297     /* Create our playlist node */
298     playlist_NodesPairCreate( pl_Get( p_sd ), _("Devices"),
299                               &p_sys->p_node_cat, &p_sys->p_node_one,
300                               true );
301
302     return VLC_SUCCESS;
303 }
304
305 static void Close( vlc_object_t *p_this )
306 {
307     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
308     services_discovery_sys_t *p_sys = p_sd->p_sys;
309
310     playlist_NodeDelete( pl_Get( p_sd ), p_sys->p_node_one, true,
311                          true );
312     playlist_NodeDelete( pl_Get( p_sd ), p_sys->p_node_cat, true,
313                          true );
314     pl_Release( p_sd );
315     free( p_sys );
316 }
317
318 static void Run( services_discovery_t* p_sd )
319 {
320     int res;
321
322     res = UpnpInit( 0, 0 );
323     if( res != UPNP_E_SUCCESS )
324     {
325         msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
326         return;
327     }
328
329     Cookie cookie;
330     cookie.serviceDiscovery = p_sd;
331     cookie.serverList = new MediaServerList( &cookie );
332
333     CallbackLock = new Lockable( &cookie );
334
335     res = UpnpRegisterClient( Callback, &cookie, &cookie.clientHandle );
336     if( res != UPNP_E_SUCCESS )
337     {
338         msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
339         goto shutDown;
340     }
341
342     res = UpnpSearchAsync( cookie.clientHandle, 5, MEDIA_SERVER_DEVICE_TYPE, &cookie );
343     if( res != UPNP_E_SUCCESS )
344     {
345         msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
346         goto shutDown;
347     }
348
349     msg_Dbg( p_sd, "UPnP discovery started" );
350     while( vlc_object_alive (p_sd) )
351     {
352         msleep( 500 );
353     }
354
355     msg_Dbg( p_sd, "UPnP discovery stopped" );
356
357  shutDown:
358     UpnpFinish();
359     delete cookie.serverList;
360     delete CallbackLock;
361 }
362
363
364 // XML utility functions:
365
366 // Returns the value of a child element, or 0 on error
367 const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName )
368 {
369     if ( !parent ) return 0;
370     if ( !tagName ) return 0;
371
372     char* s = strdup( tagName );
373     IXML_NodeList* nodeList = ixmlElement_getElementsByTagName( parent, s );
374     free( s );
375     if ( !nodeList ) return 0;
376
377     IXML_Node* element = ixmlNodeList_item( nodeList, 0 );
378     ixmlNodeList_free( nodeList );
379     if ( !element ) return 0;
380
381     IXML_Node* textNode = ixmlNode_getFirstChild( element );
382     if ( !textNode ) return 0;
383
384     return ixmlNode_getNodeValue( textNode );
385 }
386
387 // Extracts the result document from a SOAP response
388 IXML_Document* parseBrowseResult( IXML_Document* doc )
389 {
390     if ( !doc ) return 0;
391
392     IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc, "Result" );
393     if ( !resultList ) return 0;
394
395     IXML_Node* resultNode = ixmlNodeList_item( resultList, 0 );
396
397     ixmlNodeList_free( resultList );
398
399     if ( !resultNode ) return 0;
400
401     IXML_Node* textNode = ixmlNode_getFirstChild( resultNode );
402     if ( !textNode ) return 0;
403
404     const char* resultString = ixmlNode_getNodeValue( textNode );
405     char* resultXML = strdup( resultString );
406
407     IXML_Document* browseDoc = ixmlParseBuffer( resultXML );
408
409     free( resultXML );
410
411     return browseDoc;
412 }
413
414
415 // Handles all UPnP events
416 static int Callback( Upnp_EventType eventType, void* event, void* pCookie )
417 {
418     Locker locker( CallbackLock );
419
420     Cookie* cookie = ( Cookie* )pCookie;
421
422     switch( eventType ) {
423
424     case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
425     case UPNP_DISCOVERY_SEARCH_RESULT:
426     {
427         struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
428
429         IXML_Document *descriptionDoc = 0;
430
431         int res;
432         res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc );
433         if ( res != UPNP_E_SUCCESS )
434         {
435           msg_Dbg( cookie->serviceDiscovery, "%s:%d: Could not download device description!", __FILE__, __LINE__ );
436           return res;
437         }
438
439         MediaServer::parseDeviceDescription( descriptionDoc, discovery->Location, cookie );
440
441         ixmlDocument_free( descriptionDoc );
442     }
443     break;
444
445     case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
446     {
447         struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
448
449         cookie->serverList->removeServer( discovery->DeviceId );
450     }
451     break;
452
453     case UPNP_EVENT_RECEIVED:
454     {
455         Upnp_Event* e = ( Upnp_Event* )event;
456
457         MediaServer* server = cookie->serverList->getServerBySID( e->Sid );
458         if ( server ) server->fetchContents();
459     }
460     break;
461
462     case UPNP_EVENT_AUTORENEWAL_FAILED:
463     case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
464     {
465         // Re-subscribe...
466
467         Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;
468
469         MediaServer* server = cookie->serverList->getServerBySID( s->Sid );
470         if ( server ) server->subscribeToContentDirectory();
471     }
472     break;
473
474     case UPNP_EVENT_SUBSCRIBE_COMPLETE:
475         msg_Warn( cookie->serviceDiscovery, "subscription complete" );
476         break;
477  
478     case UPNP_DISCOVERY_SEARCH_TIMEOUT:
479         msg_Warn( cookie->serviceDiscovery, "search timeout" );
480         break;
481  
482     default:
483     msg_Dbg( cookie->serviceDiscovery, "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )", __FILE__, __LINE__, eventType );
484     break;
485     }
486
487     return UPNP_E_SUCCESS;
488 }
489
490
491 // Class implementations...
492
493 // MediaServer...
494
495 void MediaServer::parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie )
496 {
497     if ( !doc ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; }
498     if ( !location ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; }
499
500     const char* baseURL = location;
501
502     // Try to extract baseURL
503
504     IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" );
505     if ( urlList )
506     {
507     if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) )
508     {
509         IXML_Node* textNode = ixmlNode_getFirstChild( urlNode );
510         if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode );
511     }
512
513     ixmlNodeList_free( urlList );
514     }
515
516     // Get devices
517
518     IXML_NodeList* deviceList = ixmlDocument_getElementsByTagName( doc, "device" );
519     if ( deviceList )
520     {
521
522     for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ )
523     {
524         IXML_Element* deviceElement = ( IXML_Element* )ixmlNodeList_item( deviceList, i );
525
526         const char* deviceType = xml_getChildElementValue( deviceElement, "deviceType" );
527         if ( !deviceType ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no deviceType!", __FILE__, __LINE__ ); continue; }
528         if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 ) continue;
529
530         const char* UDN = xml_getChildElementValue( deviceElement, "UDN" );
531         if ( !UDN ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no UDN!", __FILE__, __LINE__ ); continue; }
532         if ( cookie->serverList->getServer( UDN ) != 0 ) continue;
533
534         const char* friendlyName = xml_getChildElementValue( deviceElement, "friendlyName" );
535         if ( !friendlyName ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no friendlyName!", __FILE__, __LINE__ ); continue; }
536
537         MediaServer* server = new MediaServer( UDN, friendlyName, cookie );
538         if ( !cookie->serverList->addServer( server ) ) {
539
540         delete server;
541         server = 0;
542         continue;
543         }
544
545         // Check for ContentDirectory service...
546
547         IXML_NodeList* serviceList = ixmlElement_getElementsByTagName( deviceElement, "service" );
548         if ( serviceList )
549         {
550             for ( unsigned int j = 0; j < ixmlNodeList_length( serviceList ); j++ )
551         {
552             IXML_Element* serviceElement = ( IXML_Element* )ixmlNodeList_item( serviceList, j );
553
554             const char* serviceType = xml_getChildElementValue( serviceElement, "serviceType" );
555             if ( !serviceType ) continue;
556             if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE, serviceType ) != 0 ) continue;
557
558             const char* eventSubURL = xml_getChildElementValue( serviceElement, "eventSubURL" );
559             if ( !eventSubURL ) continue;
560
561             const char* controlURL = xml_getChildElementValue( serviceElement, "controlURL" );
562             if ( !controlURL ) continue;
563
564             // Try to subscribe to ContentDirectory service
565
566             char* url = ( char* )malloc( strlen( baseURL ) + strlen( eventSubURL ) + 1 );
567             if ( url )
568             {
569                 char* s1 = strdup( baseURL );
570                 char* s2 = strdup( eventSubURL );
571
572                 if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS )
573                 {
574                 // msg_Dbg( cookie->serviceDiscovery, "CDS EVENT URL: %s", url );
575
576                 server->setContentDirectoryEventURL( url );
577                 server->subscribeToContentDirectory();
578                 }
579
580                 free( s1 );
581                 free( s2 );
582                 free( url );
583             }
584
585             // Try to browse content directory...
586
587             url = ( char* )malloc( strlen( baseURL ) + strlen( controlURL ) + 1 );
588             if ( url )
589             {
590             char* s1 = strdup( baseURL );
591             char* s2 = strdup( controlURL );
592
593             if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS )
594             {
595                 // msg_Dbg( cookie->serviceDiscovery, "CDS CTRL URL: %s", url );
596
597                 server->setContentDirectoryControlURL( url );
598                 server->fetchContents();
599             }
600
601             free( s1 );
602             free( s2 );
603             free( url );
604             }
605         }
606
607         ixmlNodeList_free( serviceList );
608         }
609     }
610
611     ixmlNodeList_free( deviceList );
612     }
613 }
614
615 MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie )
616 {
617     _cookie = cookie;
618
619     _UDN = UDN;
620     _friendlyName = friendlyName;
621
622     _contents = 0;
623     _playlistNode = 0;
624 }
625
626 MediaServer::~MediaServer()
627 {
628     if ( _contents )
629     {
630         playlist_NodeDelete( pl_Get( _cookie->serviceDiscovery ) ,
631                              _playlistNode, true, true );
632     }
633
634     delete _contents;
635 }
636
637 const char* MediaServer::getUDN() const
638 {
639   const char* s = _UDN.c_str();
640   return s;
641 }
642
643 const char* MediaServer::getFriendlyName() const
644 {
645     const char* s = _friendlyName.c_str();
646     return s;
647 }
648
649 void MediaServer::setContentDirectoryEventURL( const char* url )
650 {
651     _contentDirectoryEventURL = url;
652 }
653
654 const char* MediaServer::getContentDirectoryEventURL() const
655 {
656     const char* s =  _contentDirectoryEventURL.c_str();
657     return s;
658 }
659
660 void MediaServer::setContentDirectoryControlURL( const char* url )
661 {
662     _contentDirectoryControlURL = url;
663 }
664
665 const char* MediaServer::getContentDirectoryControlURL() const
666 {
667     return _contentDirectoryControlURL.c_str();
668 }
669
670 void MediaServer::subscribeToContentDirectory()
671 {
672     const char* url = getContentDirectoryEventURL();
673     if ( !url || strcmp( url, "" ) == 0 )
674     {
675     msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" );
676     return;
677     }
678
679     int timeOut = 1810;
680     Upnp_SID sid;
681
682     int res = UpnpSubscribe( _cookie->clientHandle, url, &timeOut, sid );
683
684     if ( res == UPNP_E_SUCCESS )
685     {
686     _subscriptionTimeOut = timeOut;
687     memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
688     }
689     else
690     {
691     msg_Dbg( _cookie->serviceDiscovery, "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__, getFriendlyName(), UpnpGetErrorMessage( res ) );
692     }
693 }
694
695 IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pBrowseFlag, const char* pFilter,
696                        const char* pStartingIndex, const char* pRequestedCount, const char* pSortCriteria )
697 {
698     IXML_Document* action = 0;
699     IXML_Document* response = 0;
700
701     const char* url = getContentDirectoryControlURL();
702     if ( !url || strcmp( url, "" ) == 0 ) { msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" ); return 0; }
703
704     char* ObjectID = strdup( pObjectID );
705     char* BrowseFlag = strdup( pBrowseFlag );
706     char* Filter = strdup( pFilter );
707     char* StartingIndex = strdup( pStartingIndex );
708     char* RequestedCount = strdup( pRequestedCount );
709     char* SortCriteria = strdup( pSortCriteria );
710
711     char* serviceType = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
712
713     int res;
714
715     res = UpnpAddToAction( &action, "Browse", serviceType, "ObjectID", ObjectID );
716     if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
717
718     res = UpnpAddToAction( &action, "Browse", serviceType, "BrowseFlag", BrowseFlag );
719     if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
720
721     res = UpnpAddToAction( &action, "Browse", serviceType, "Filter", Filter );
722     if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
723
724     res = UpnpAddToAction( &action, "Browse", serviceType, "StartingIndex", StartingIndex );
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, "RequestedCount", RequestedCount );
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, "SortCriteria", SortCriteria );
731     if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
732
733     res = UpnpSendAction( _cookie->clientHandle,
734               url,
735               CONTENT_DIRECTORY_SERVICE_TYPE,
736               0,
737               action,
738               &response );
739     if ( res != UPNP_E_SUCCESS )
740     {
741     msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) );
742     ixmlDocument_free( response );
743     response = 0;
744     }
745
746  browseActionCleanup:
747
748     free( ObjectID );
749     free( BrowseFlag );
750     free( Filter );
751     free( StartingIndex );
752     free( RequestedCount );
753     free( SortCriteria );
754
755     free( serviceType );
756
757     ixmlDocument_free( action );
758     return response;
759 }
760
761 void MediaServer::fetchContents()
762 {
763     Container* root = new Container( 0, "0", getFriendlyName() );
764     playlist_t * p_playlist = pl_Get( _cookie->serviceDiscovery );
765     _fetchContents( root );
766
767     if ( _contents )
768     {
769         PL_LOCK;
770         playlist_NodeEmpty( p_playlist, _playlistNode, true );
771         PL_UNLOCK;
772         delete _contents;
773     }
774
775     _contents = root;
776     _contents->setPlaylistNode( _playlistNode );
777
778     _buildPlaylist( _contents );
779 }
780
781 bool MediaServer::_fetchContents( Container* parent )
782 {
783     if (!parent) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: parent==NULL", __FILE__, __LINE__ ); return false; }
784
785     IXML_Document* response = _browseAction( parent->getObjectID(), "BrowseDirectChildren", "*", "0", "0", "" );
786     if ( !response ) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR!", __FILE__, __LINE__ ); return false; }
787
788     IXML_Document* result = parseBrowseResult( response );
789     ixmlDocument_free( response );
790     if ( !result ) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR!", __FILE__, __LINE__ ); return false; }
791
792     IXML_NodeList* containerNodeList = ixmlDocument_getElementsByTagName( result, "container" );
793     if ( containerNodeList )
794     {
795     for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ )
796     {
797               IXML_Element* containerElement = ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
798
799         const char* objectID = ixmlElement_getAttribute( containerElement, "id" );
800         if ( !objectID ) continue;
801
802         const char* childCountStr = ixmlElement_getAttribute( containerElement, "childCount" );
803         if ( !childCountStr ) continue;
804         int childCount = atoi( childCountStr );
805
806         const char* title = xml_getChildElementValue( containerElement, "dc:title" );
807         if ( !title ) continue;
808
809         const char* resource = xml_getChildElementValue( containerElement, "res" );
810
811         if ( resource && childCount < 1 )
812         {
813         Item* item = new Item( parent, objectID, title, resource );
814         parent->addItem( item );
815         }
816         else
817         {
818         Container* container = new Container( parent, objectID, title );
819         parent->addContainer( container );
820
821         if ( childCount > 0 ) _fetchContents( container );
822         }
823     }
824
825     ixmlNodeList_free( containerNodeList );
826     }
827
828     IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result, "item" );
829     if ( itemNodeList )
830     {
831         for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
832     {
833         IXML_Element* itemElement = ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
834
835         const char* objectID = ixmlElement_getAttribute( itemElement, "id" );
836         if ( !objectID ) continue;
837
838         const char* title = xml_getChildElementValue( itemElement, "dc:title" );
839         if ( !title ) continue;
840
841         const char* resource = xml_getChildElementValue( itemElement, "res" );
842         if ( !resource ) continue;
843
844         Item* item = new Item( parent, objectID, title, resource );
845         parent->addItem( item );
846     }
847
848     ixmlNodeList_free( itemNodeList );
849     }
850
851     ixmlDocument_free( result );
852
853     return true;
854 }
855
856 void MediaServer::_buildPlaylist( Container* parent )
857 {
858     playlist_t *p_playlist = pl_Get( _cookie->serviceDiscovery );
859     for ( unsigned int i = 0; i < parent->getNumContainers(); i++ )
860     {
861         Container* container = parent->getContainer( i );
862         playlist_item_t* parentNode = parent->getPlaylistNode();
863
864         char* title = strdup( container->getTitle() );
865         playlist_item_t* node = playlist_NodeCreate( p_playlist, title, parentNode, 0, NULL );
866         free( title );
867
868         container->setPlaylistNode( node );
869         _buildPlaylist( container );
870     }
871
872     for ( unsigned int i = 0; i < parent->getNumItems(); i++ )
873     {
874         Item* item = parent->getItem( i );
875         playlist_item_t* parentNode = parent->getPlaylistNode();
876
877         input_item_t* p_input = input_ItemNew( _cookie->serviceDiscovery,
878                                                item->getResource(),
879                                                item->getTitle() );
880         int i_cat;
881         /* FIXME: playlist_AddInput() can fail */
882         playlist_BothAddInput( p_playlist, p_input, parentNode,
883                                PLAYLIST_APPEND, PLAYLIST_END, &i_cat, NULL,
884                                pl_Unlocked );
885         vlc_gc_decref( p_input );
886         /* TODO: do this better by storing ids */
887         playlist_item_t *p_node = playlist_ItemGetById( p_playlist, i_cat, false );
888         assert( p_node );
889         item->setPlaylistNode( p_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( pl_Get( _cookie->serviceDiscovery ),
929                                                  name,
930                                           _cookie->serviceDiscovery->p_sys->p_node_cat, 0, NULL );
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 }