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