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