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