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