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