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