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