Unduplicate UPnP long string
[vlc.git] / 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
258 // Module descriptor
259
260 vlc_module_begin();
261 set_shortname( "UPnP" );
262 set_description( N_( "Universal Plug'n'Play discovery" ) );
263 set_category( CAT_PLAYLIST );
264 set_subcategory( SUBCAT_PLAYLIST_SD );
265 set_capability( "services_discovery", 0 );
266 set_callbacks( Open, Close );
267 vlc_module_end();
268
269
270 // More prototypes...
271
272 static int Callback( Upnp_EventType eventType, void* event, void* user_data );
273
274 const char* xml_getChildElementValue( IXML_Element* parent,
275                                       const char*   tagName );
276
277 IXML_Document* parseBrowseResult( IXML_Document* doc );
278
279
280 // VLC callbacks...
281
282 static int Open( vlc_object_t *p_this )
283 {
284     int res;
285     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
286     services_discovery_sys_t *p_sys  = ( services_discovery_sys_t * )
287             calloc( 1, sizeof( services_discovery_sys_t ) );
288
289     if(!(p_sd->p_sys = p_sys))
290         return VLC_ENOMEM;
291
292     services_discovery_SetLocalizedName( p_sd, _("UPnP devices") );
293
294     res = UpnpInit( 0, 0 );
295     if( res != UPNP_E_SUCCESS )
296     {
297         msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
298         return VLC_EGENERIC;
299     }
300
301     p_sys->serverList = new MediaServerList( p_sd );
302     p_sys->callbackLock = new Lockable();
303
304     res = UpnpRegisterClient( Callback, p_sd, &p_sys->clientHandle );
305     if( res != UPNP_E_SUCCESS )
306     {
307         msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
308         Close( (vlc_object_t*) p_sd );
309         return VLC_EGENERIC;
310     }
311
312     res = UpnpSearchAsync( p_sys->clientHandle, 5,
313             MEDIA_SERVER_DEVICE_TYPE, p_sd );
314     
315     if( res != UPNP_E_SUCCESS )
316     {
317         msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
318         Close( (vlc_object_t*) p_sd );
319         return VLC_EGENERIC;
320     }
321
322     return VLC_SUCCESS;
323 }
324
325 static void Close( vlc_object_t *p_this )
326 {
327     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
328
329     UpnpFinish();
330     delete p_sd->p_sys->serverList;
331     delete p_sd->p_sys->callbackLock;
332
333     free( p_sd->p_sys );
334 }
335
336 // XML utility functions:
337
338 // Returns the value of a child element, or 0 on error
339 const char* xml_getChildElementValue( IXML_Element* parent,
340                                       const char*   tagName )
341 {
342     if ( !parent ) return 0;
343     if ( !tagName ) return 0;
344
345     char* s = strdup( tagName );
346     IXML_NodeList* nodeList = ixmlElement_getElementsByTagName( parent, s );
347     free( s );
348     if ( !nodeList ) return 0;
349
350     IXML_Node* element = ixmlNodeList_item( nodeList, 0 );
351     ixmlNodeList_free( nodeList );
352     if ( !element ) return 0;
353
354     IXML_Node* textNode = ixmlNode_getFirstChild( element );
355     if ( !textNode ) return 0;
356
357     return ixmlNode_getNodeValue( textNode );
358 }
359
360 // Extracts the result document from a SOAP response
361 IXML_Document* parseBrowseResult( IXML_Document* doc )
362 {
363     ixmlRelaxParser(1);
364
365     if ( !doc ) return 0;
366
367     IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc,
368                                                                    "Result" );
369
370     if ( !resultList ) return 0;
371
372     IXML_Node* resultNode = ixmlNodeList_item( resultList, 0 );
373
374     ixmlNodeList_free( resultList );
375
376     if ( !resultNode ) return 0;
377
378     IXML_Node* textNode = ixmlNode_getFirstChild( resultNode );
379     if ( !textNode ) return 0;
380
381     const char* resultString = ixmlNode_getNodeValue( textNode );
382     char* resultXML = strdup( resultString );
383
384     IXML_Document* browseDoc = ixmlParseBuffer( resultXML );
385
386     free( resultXML );
387
388     return browseDoc;
389 }
390
391
392 // Handles all UPnP events
393 static int Callback( Upnp_EventType eventType, void* event, void* user_data )
394 {
395     services_discovery_t *p_sd = ( services_discovery_t* ) user_data;
396     services_discovery_sys_t* p_sys = p_sd->p_sys;
397     Locker locker( p_sys->callbackLock );
398
399     switch( eventType ) {
400
401     case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
402     case UPNP_DISCOVERY_SEARCH_RESULT:
403     {
404         struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
405
406         IXML_Document *descriptionDoc = 0;
407
408         int res;
409         res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc );
410         if ( res != UPNP_E_SUCCESS )
411         {
412             msg_Dbg( p_sd,
413                     "%s:%d: Could not download device description!",
414                     __FILE__, __LINE__ );
415             return res;
416         }
417
418         MediaServer::parseDeviceDescription( descriptionDoc,
419                 discovery->Location, p_sd );
420
421         ixmlDocument_free( descriptionDoc );
422     }
423     break;
424
425     case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
426     {
427         struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
428
429         p_sys->serverList->removeServer( discovery->DeviceId );
430     }
431     break;
432
433     case UPNP_EVENT_RECEIVED:
434     {
435         Upnp_Event* e = ( Upnp_Event* )event;
436
437         MediaServer* server = p_sys->serverList->getServerBySID( e->Sid );
438         if ( server ) server->fetchContents();
439     }
440     break;
441
442     case UPNP_EVENT_AUTORENEWAL_FAILED:
443     case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
444     {
445         // Re-subscribe...
446
447         Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;
448
449         MediaServer* server = p_sys->serverList->getServerBySID( s->Sid );
450         if ( server ) server->subscribeToContentDirectory();
451     }
452     break;
453
454     case UPNP_EVENT_SUBSCRIBE_COMPLETE:
455         msg_Warn( p_sd, "subscription complete" );
456         break;
457  
458     case UPNP_DISCOVERY_SEARCH_TIMEOUT:
459         msg_Warn( p_sd, "search timeout" );
460         break;
461  
462     default:
463     msg_Dbg( p_sd,
464             "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )",
465             __FILE__, __LINE__, eventType );
466     break;
467     }
468
469     return UPNP_E_SUCCESS;
470 }
471
472
473 // Class implementations...
474
475 // MediaServer...
476
477 void MediaServer::parseDeviceDescription( IXML_Document* doc,
478                                           const char*    location,
479                                           services_discovery_t* p_sd )
480 {
481     if ( !doc )
482     { 
483         msg_Dbg( p_sd, "%s:%d: NULL", __FILE__, __LINE__ ); 
484         return;
485     }
486     
487     if ( !location )
488     {
489         msg_Dbg( p_sd, "%s:%d: NULL", __FILE__, __LINE__ ); 
490         return;
491     }
492
493     const char* baseURL = location;
494
495     // Try to extract baseURL
496
497     IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" );
498     if ( !urlList )
499     {
500
501         if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) )
502         {
503             IXML_Node* textNode = ixmlNode_getFirstChild( urlNode );
504             if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode );
505         }
506
507         ixmlNodeList_free( urlList );
508     }
509
510     // Get devices
511
512     IXML_NodeList* deviceList =
513                 ixmlDocument_getElementsByTagName( doc, "device" );
514     
515     if ( deviceList )
516     {
517         for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ )
518         {
519             IXML_Element* deviceElement =
520                    ( IXML_Element* ) ixmlNodeList_item( deviceList, i );
521
522             const char* deviceType = xml_getChildElementValue( deviceElement,
523                                                                "deviceType" );
524             if ( !deviceType )
525             {
526                 msg_Dbg( p_sd,
527                         "%s:%d: no deviceType!",
528                         __FILE__, __LINE__ );
529                 continue;
530             }
531
532             if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 )
533                 continue;
534
535             const char* UDN = xml_getChildElementValue( deviceElement, "UDN" );
536             if ( !UDN )
537             {
538                 msg_Dbg( p_sd, "%s:%d: no UDN!",
539                         __FILE__, __LINE__ );
540                 continue;
541             }
542             
543             if ( p_sd->p_sys->serverList->getServer( UDN ) != 0 )
544                 continue;
545
546             const char* friendlyName =
547                        xml_getChildElementValue( deviceElement, 
548                                                  "friendlyName" );
549             
550             if ( !friendlyName )
551             {
552                 msg_Dbg( p_sd, "%s:%d: no friendlyName!", __FILE__, __LINE__ );
553                 continue;
554             }
555
556             MediaServer* server = new MediaServer( UDN, friendlyName, p_sd );
557             
558             if ( !p_sd->p_sys->serverList->addServer( server ) )
559             {
560
561                 delete server;
562                 server = 0;
563                 continue;
564             }
565
566             // Check for ContentDirectory service...
567             IXML_NodeList* serviceList =
568                        ixmlElement_getElementsByTagName( deviceElement,
569                                                          "service" );
570             if ( serviceList )
571             {
572                 for ( unsigned int j = 0;
573                       j < ixmlNodeList_length( serviceList ); j++ )
574                 {
575                     IXML_Element* serviceElement =
576                         ( IXML_Element* ) ixmlNodeList_item( serviceList, j );
577
578                     const char* serviceType =
579                         xml_getChildElementValue( serviceElement,
580                                                   "serviceType" );
581                     if ( !serviceType )
582                         continue;
583
584                     if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE,
585                                 serviceType ) != 0 )
586                         continue;
587
588                     const char* eventSubURL =
589                         xml_getChildElementValue( serviceElement,
590                                                   "eventSubURL" );
591                     if ( !eventSubURL )
592                         continue;
593
594                     const char* controlURL =
595                         xml_getChildElementValue( serviceElement,
596                                                   "controlURL" );
597                     if ( !controlURL )
598                         continue;
599
600                     // Try to subscribe to ContentDirectory service
601
602                     char* url = ( char* ) malloc( strlen( baseURL ) +
603                             strlen( eventSubURL ) + 1 );
604                     if ( url )
605                     {
606                         char* s1 = strdup( baseURL );
607                         char* s2 = strdup( eventSubURL );
608
609                         if ( UpnpResolveURL( s1, s2, url ) ==
610                                 UPNP_E_SUCCESS )
611                         {
612                             server->setContentDirectoryEventURL( url );
613                             server->subscribeToContentDirectory();
614                         }
615
616                         free( s1 );
617                         free( s2 );
618                         free( url );
619                     }
620
621                     // Try to browse content directory...
622
623                     url = ( char* ) malloc( strlen( baseURL ) +
624                             strlen( controlURL ) + 1 );
625                     if ( url )
626                     {
627                         char* s1 = strdup( baseURL );
628                         char* s2 = strdup( controlURL );
629
630                         if ( UpnpResolveURL( s1, s2, url ) ==
631                                 UPNP_E_SUCCESS )
632                         {
633                             server->setContentDirectoryControlURL( url );
634                             server->fetchContents();
635                         }
636
637                         free( s1 );
638                         free( s2 );
639                         free( url );
640                     }
641                }
642                ixmlNodeList_free( serviceList );
643            }
644        }
645        ixmlNodeList_free( deviceList );
646     }
647 }
648
649 MediaServer::MediaServer( const char* UDN,
650                           const char* friendlyName,
651                           services_discovery_t* p_sd )
652 {
653     _p_sd = p_sd;
654
655     _UDN = UDN;
656     _friendlyName = friendlyName;
657
658     _contents = NULL;
659     _inputItem = NULL;
660 }
661
662 MediaServer::~MediaServer()
663 {
664     delete _contents;
665 }
666
667 const char* MediaServer::getUDN() const
668 {
669   const char* s = _UDN.c_str();
670   return s;
671 }
672
673 const char* MediaServer::getFriendlyName() const
674 {
675     const char* s = _friendlyName.c_str();
676     return s;
677 }
678
679 void MediaServer::setContentDirectoryEventURL( const char* url )
680 {
681     _contentDirectoryEventURL = url;
682 }
683
684 const char* MediaServer::getContentDirectoryEventURL() const
685 {
686     const char* s =  _contentDirectoryEventURL.c_str();
687     return s;
688 }
689
690 void MediaServer::setContentDirectoryControlURL( const char* url )
691 {
692     _contentDirectoryControlURL = url;
693 }
694
695 const char* MediaServer::getContentDirectoryControlURL() const
696 {
697     return _contentDirectoryControlURL.c_str();
698 }
699
700 void MediaServer::subscribeToContentDirectory()
701 {
702     const char* url = getContentDirectoryEventURL();
703     if ( !url || strcmp( url, "" ) == 0 )
704     {
705         msg_Dbg( _p_sd, "No subscription url set!" );
706         return;
707     }
708
709     int timeOut = 1810;
710     Upnp_SID sid;
711
712     int res = UpnpSubscribe( _p_sd->p_sys->clientHandle, url, &timeOut, sid );
713
714     if ( res == UPNP_E_SUCCESS )
715     {
716         _subscriptionTimeOut = timeOut;
717         memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
718     }
719     else
720     {
721         msg_Dbg( _p_sd,
722                 "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__,
723                 getFriendlyName(), UpnpGetErrorMessage( res ) );
724     }
725 }
726
727 IXML_Document* MediaServer::_browseAction( const char* pObjectID,
728                                            const char* pBrowseFlag,
729                                            const char* pFilter,
730                                            const char* pStartingIndex,
731                                            const char* pRequestedCount,
732                                            const char* pSortCriteria )
733 {
734     IXML_Document* action = 0;
735     IXML_Document* response = 0;
736     const char* url = getContentDirectoryControlURL();
737     
738     if ( !url || strcmp( url, "" ) == 0 )
739     {
740         msg_Dbg( _p_sd, "No subscription url set!" );
741         return 0;
742     }
743
744     char* ObjectID = strdup( pObjectID );
745     char* BrowseFlag = strdup( pBrowseFlag );
746     char* Filter = strdup( pFilter );
747     char* StartingIndex = strdup( pStartingIndex );
748     char* RequestedCount = strdup( pRequestedCount );
749     char* SortCriteria = strdup( pSortCriteria );
750     char* serviceType = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
751
752     int res;
753
754     res = UpnpAddToAction( &action, "Browse",
755             serviceType, "ObjectID", ObjectID );
756     
757     if ( res != UPNP_E_SUCCESS ) 
758     {
759         msg_Dbg( _p_sd,
760                  "%s:%d: ERROR: %s", __FILE__, __LINE__,
761                  UpnpGetErrorMessage( res ) );
762         goto browseActionCleanup;
763     }
764
765     res = UpnpAddToAction( &action, "Browse",
766             serviceType, "BrowseFlag", BrowseFlag );
767     
768     if ( res != UPNP_E_SUCCESS )
769     {
770         msg_Dbg( _p_sd,
771              "%s:%d: ERROR: %s", __FILE__, __LINE__,
772              UpnpGetErrorMessage( res ) );
773         goto browseActionCleanup;
774     }
775
776     res = UpnpAddToAction( &action, "Browse",
777             serviceType, "Filter", Filter );
778     
779     if ( res != UPNP_E_SUCCESS )
780     {
781         msg_Dbg( _p_sd,
782              "%s:%d: ERROR: %s", __FILE__, __LINE__,
783              UpnpGetErrorMessage( res ) );
784         goto browseActionCleanup;
785     }
786
787     res = UpnpAddToAction( &action, "Browse",
788             serviceType, "StartingIndex", StartingIndex );
789
790     if ( res != UPNP_E_SUCCESS )
791     {
792         msg_Dbg( _p_sd,
793              "%s:%d: ERROR: %s", __FILE__, __LINE__,
794              UpnpGetErrorMessage( res ) );
795         goto browseActionCleanup;
796     }
797
798     res = UpnpAddToAction( &action, "Browse",
799             serviceType, "RequestedCount", RequestedCount );
800
801     if ( res != UPNP_E_SUCCESS )
802     {
803         msg_Dbg( _p_sd,
804                 "%s:%d: ERROR: %s", __FILE__, __LINE__,
805                 UpnpGetErrorMessage( res ) ); goto browseActionCleanup; }
806
807     res = UpnpAddToAction( &action, "Browse",
808             serviceType, "SortCriteria", SortCriteria );
809     
810     if ( res != UPNP_E_SUCCESS )
811     {
812         msg_Dbg( _p_sd,
813              "%s:%d: ERROR: %s", __FILE__, __LINE__,
814              UpnpGetErrorMessage( res ) );
815         goto browseActionCleanup;
816     }
817
818     res = UpnpSendAction( _p_sd->p_sys->clientHandle,
819               url,
820               CONTENT_DIRECTORY_SERVICE_TYPE,
821               0,
822               action,
823               &response );
824     
825     if ( res != UPNP_E_SUCCESS )
826     {
827         msg_Dbg( _p_sd,
828                 "%s:%d: ERROR: %s when trying the send() action with URL: %s",
829                 __FILE__, __LINE__,
830                 UpnpGetErrorMessage( res ), url );
831
832         ixmlDocument_free( response );
833         response = 0;
834     }
835
836  browseActionCleanup:
837
838     free( ObjectID );
839     free( BrowseFlag );
840     free( Filter );
841     free( StartingIndex );
842     free( RequestedCount );
843     free( SortCriteria );
844
845     free( serviceType );
846
847     ixmlDocument_free( action );
848     return response;
849 }
850
851 void MediaServer::fetchContents()
852 {
853     Container* root = new Container( 0, "0", getFriendlyName() );
854     _fetchContents( root );
855
856    // if ( _contents )
857    // {
858    //     PL_LOCK;
859    //     playlist_NodeEmpty( p_playlist, _playlistNode, true );
860    //     PL_UNLOCK;
861    //     delete _contents;
862    // }
863
864     _contents = root;
865     _contents->setInputItem( _inputItem );
866
867     _buildPlaylist( _contents );
868 }
869
870 bool MediaServer::_fetchContents( Container* parent )
871 {
872     if (!parent)
873     {
874         msg_Dbg( _p_sd,
875                 "%s:%d: parent==NULL", __FILE__, __LINE__ );
876         return false;
877     }
878
879     IXML_Document* response = _browseAction( parent->getObjectID(),
880                                       "BrowseDirectChildren",
881                                       "*", "0", "0", "" );
882     if ( !response )
883     {
884         msg_Dbg( _p_sd,
885                 "%s:%d: ERROR! No response from browse() action",
886                 __FILE__, __LINE__ );
887         return false;
888     }
889
890     IXML_Document* result = parseBrowseResult( response );
891     ixmlDocument_free( response );
892     
893     if ( !result )
894     {
895         msg_Dbg( _p_sd,
896                 "%s:%d: ERROR! browse() response parsing failed",
897                 __FILE__, __LINE__ );
898         return false;
899     }
900
901     IXML_NodeList* containerNodeList =
902                 ixmlDocument_getElementsByTagName( result, "container" );
903     
904     if ( containerNodeList )
905     {
906         for ( unsigned int i = 0;
907                 i < ixmlNodeList_length( containerNodeList ); i++ )
908         {
909             IXML_Element* containerElement =
910                   ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
911
912             const char* objectID = ixmlElement_getAttribute( containerElement,
913                                                              "id" );
914             if ( !objectID )
915                 continue;
916
917             const char* childCountStr =
918                     ixmlElement_getAttribute( containerElement, "childCount" );
919             
920             if ( !childCountStr )
921                 continue;
922             
923             int childCount = atoi( childCountStr );
924             const char* title = xml_getChildElementValue( containerElement,
925                                                           "dc:title" );
926             
927             if ( !title )
928                 continue;
929             
930             const char* resource = xml_getChildElementValue( containerElement,
931                                                              "res" );
932
933             if ( resource && childCount < 1 )
934             {
935                 Item* item = new Item( parent, objectID, title, resource );
936                 parent->addItem( item );
937             }
938
939             else
940             {
941                 Container* container = new Container( parent, objectID, title );
942                 parent->addContainer( container );
943
944                 if ( childCount > 0 )
945                     _fetchContents( container );
946             }
947         }
948         ixmlNodeList_free( containerNodeList );
949     }
950
951     IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result,
952                                                                      "item" );
953     if ( itemNodeList )
954     {
955         for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
956         {
957             IXML_Element* itemElement =
958                         ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
959
960             const char* objectID =
961                         ixmlElement_getAttribute( itemElement, "id" );
962             
963             if ( !objectID )
964                 continue;
965
966             const char* title =
967                         xml_getChildElementValue( itemElement, "dc:title" );
968             
969             if ( !title )
970                 continue;
971
972             const char* resource =
973                         xml_getChildElementValue( itemElement, "res" );
974             
975             if ( !resource )
976                 continue;
977
978             Item* item = new Item( parent, objectID, title, resource );
979             parent->addItem( item );
980         }
981         ixmlNodeList_free( itemNodeList );
982     }
983
984     ixmlDocument_free( result );
985     return true;
986 }
987
988 void MediaServer::_buildPlaylist( Container* parent )
989 {
990     for ( unsigned int i = 0; i < parent->getNumContainers(); i++ )
991     {
992         Container* container = parent->getContainer( i );
993
994         input_item_t* p_input_item = input_item_New( _p_sd, "vlc://nop", parent->getTitle() ); 
995         input_item_AddSubItem( parent->getInputItem(), p_input_item );
996
997         container->setInputItem( p_input_item );
998         _buildPlaylist( container );
999     }
1000
1001     for ( unsigned int i = 0; i < parent->getNumItems(); i++ )
1002     {
1003         Item* item = parent->getItem( i );
1004
1005         input_item_t* p_input_item = input_item_New( _p_sd,
1006                                                item->getResource(),
1007                                                item->getTitle() );
1008         assert( p_input_item );
1009         input_item_AddSubItem( parent->getInputItem(), p_input_item );
1010         item->setInputItem( p_input_item );
1011     }
1012 }
1013
1014 void MediaServer::setInputItem( input_item_t* p_input_item )
1015 {
1016     if(_inputItem == p_input_item)
1017         return;
1018
1019     if(_inputItem)
1020         vlc_gc_decref( _inputItem );
1021
1022     vlc_gc_incref( p_input_item );
1023     _inputItem = p_input_item;
1024 }
1025
1026 bool MediaServer::compareSID( const char* sid )
1027 {
1028     return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 );
1029 }
1030
1031
1032 // MediaServerList...
1033
1034 MediaServerList::MediaServerList( services_discovery_t* p_sd )
1035 {
1036     _p_sd = p_sd;
1037 }
1038
1039 MediaServerList::~MediaServerList()
1040 {
1041     for ( unsigned int i = 0; i < _list.size(); i++ )
1042     {
1043         delete _list[i];
1044     }
1045 }
1046
1047 bool MediaServerList::addServer( MediaServer* s )
1048 {
1049     input_item_t* p_input_item = NULL;
1050     if ( getServer( s->getUDN() ) != 0 ) return false;
1051
1052     msg_Dbg( _p_sd, "Adding server '%s'",
1053             s->getFriendlyName() );
1054
1055     services_discovery_t* p_sd = _p_sd;
1056
1057     p_input_item = input_item_New( p_sd, "vlc://nop", s->getFriendlyName() ); 
1058     s->setInputItem( p_input_item );
1059
1060     services_discovery_AddItem( p_sd, p_input_item, NULL );
1061
1062     _list.push_back( s );
1063
1064     return true;
1065 }
1066
1067 MediaServer* MediaServerList::getServer( const char* UDN )
1068 {
1069     MediaServer* result = 0;
1070
1071     for ( unsigned int i = 0; i < _list.size(); i++ )
1072     {
1073         if( strcmp( UDN, _list[i]->getUDN() ) == 0 )
1074         {
1075             result = _list[i];
1076             break;
1077         }
1078     }
1079
1080     return result;
1081 }
1082
1083 MediaServer* MediaServerList::getServerBySID( const char* sid )
1084 {
1085     MediaServer* server = 0;
1086
1087     for ( unsigned int i = 0; i < _list.size(); i++ )
1088     {
1089         if ( _list[i]->compareSID( sid ) )
1090         {
1091             server = _list[i];
1092             break;
1093         }
1094     }
1095
1096     return server;
1097 }
1098
1099 void MediaServerList::removeServer( const char* UDN )
1100 {
1101     MediaServer* server = getServer( UDN );
1102     if ( !server ) return;
1103
1104     msg_Dbg( _p_sd,
1105             "Removing server '%s'", server->getFriendlyName() );
1106
1107     std::vector<MediaServer*>::iterator it;
1108     for ( it = _list.begin(); it != _list.end(); it++ )
1109     {
1110         if ( *it == server )
1111         {
1112             _list.erase( it );
1113             delete server;
1114             break;
1115         }
1116     }
1117 }
1118
1119
1120 // Item...
1121
1122 Item::Item( Container* parent, const char* objectID, const char* title, const char* resource )
1123 {
1124     _parent = parent;
1125
1126     _objectID = objectID;
1127     _title = title;
1128     _resource = resource;
1129
1130     _inputItem = NULL;
1131 }
1132
1133 Item::~Item()
1134 {
1135     if(_inputItem)
1136         vlc_gc_decref( _inputItem );
1137 }
1138
1139 const char* Item::getObjectID() const
1140 {
1141     return _objectID.c_str();
1142 }
1143
1144 const char* Item::getTitle() const
1145 {
1146     return _title.c_str();
1147 }
1148
1149 const char* Item::getResource() const
1150 {
1151     return _resource.c_str();
1152 }
1153
1154 void Item::setInputItem( input_item_t* p_input_item )
1155 {
1156     if(_inputItem == p_input_item)
1157         return;
1158
1159     if(_inputItem)
1160         vlc_gc_decref( _inputItem );
1161
1162     vlc_gc_incref( p_input_item );
1163     _inputItem = p_input_item;
1164 }
1165
1166 input_item_t* Item::getInputItem() const
1167 {
1168     return _inputItem;
1169 }
1170
1171
1172 // Container...
1173
1174 Container::Container( Container*  parent,
1175                       const char* objectID,
1176                       const char* title )
1177 {
1178     _parent = parent;
1179
1180     _objectID = objectID;
1181     _title = title;
1182
1183     _inputItem = NULL;
1184 }
1185
1186 Container::~Container()
1187 {
1188     for ( unsigned int i = 0; i < _containers.size(); i++ )
1189     {
1190         delete _containers[i];
1191     }
1192
1193     for ( unsigned int i = 0; i < _items.size(); i++ )
1194     {
1195         delete _items[i];
1196     }
1197
1198     if(_inputItem )
1199         vlc_gc_decref( _inputItem );
1200 }
1201
1202 void Container::addItem( Item* item )
1203 {
1204     _items.push_back( item );
1205 }
1206
1207 void Container::addContainer( Container* container )
1208 {
1209     _containers.push_back( container );
1210 }
1211
1212 const char* Container::getObjectID() const
1213 {
1214     return _objectID.c_str();
1215 }
1216
1217 const char* Container::getTitle() const
1218 {
1219     return _title.c_str();
1220 }
1221
1222 unsigned int Container::getNumItems() const
1223 {
1224     return _items.size();
1225 }
1226
1227 unsigned int Container::getNumContainers() const
1228 {
1229     return _containers.size();
1230 }
1231
1232 Item* Container::getItem( unsigned int i ) const
1233 {
1234     if ( i < _items.size() ) return _items[i];
1235     return 0;
1236 }
1237
1238 Container* Container::getContainer( unsigned int i ) const
1239 {
1240     if ( i < _containers.size() ) return _containers[i];
1241     return 0;
1242 }
1243
1244 Container* Container::getParent()
1245 {
1246     return _parent;
1247 }
1248
1249 void Container::setInputItem( input_item_t* p_input_item )
1250 {
1251     if(_inputItem == p_input_item)
1252         return;
1253
1254     if(_inputItem)
1255         vlc_gc_decref( _inputItem );
1256
1257     vlc_gc_incref( p_input_item );
1258     _inputItem = p_input_item;
1259 }
1260
1261 input_item_t* Container::getInputItem() const
1262 {
1263     return _inputItem;
1264 }