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