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