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