/***************************************************************************** * Upnp_intell.cpp : UPnP discovery module (Intel SDK) ***************************************************************************** * Copyright (C) 2004-2006 the VideoLAN team * $Id$ * * Authors: Rémi Denis-Courmont (original plugin) * Christian Henz * * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ /* \TODO: Debug messages: "__FILE__, __LINE__" ok ???, Wrn/Err ??? \TODO: Change names to VLC standard ??? \TODO: Rewrite this using the new service discovery API (see sap.c, shout.c). */ #include #include #include #include #undef PACKAGE_NAME #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include // VLC handle struct services_discovery_sys_t { playlist_item_t *p_node_cat; playlist_item_t *p_node_one; }; // Constants const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1"; const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1"; // Classes class MediaServer; class MediaServerList; class Item; class Container; // Cookie that is passed to the callback typedef struct { services_discovery_t* serviceDiscovery; UpnpClient_Handle clientHandle; MediaServerList* serverList; } Cookie; // Class definitions... class Lockable { public: Lockable( Cookie* c ) { vlc_mutex_init( &_mutex ); } ~Lockable() { vlc_mutex_destroy( &_mutex ); } void lock() { vlc_mutex_lock( &_mutex ); } void unlock() { vlc_mutex_unlock( &_mutex ); } private: vlc_mutex_t _mutex; }; class Locker { public: Locker( Lockable* l ) { _lockable = l; _lockable->lock(); } ~Locker() { _lockable->unlock(); } private: Lockable* _lockable; }; class MediaServer { public: static void parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie ); MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie ); ~MediaServer(); const char* getUDN() const; const char* getFriendlyName() const; void setContentDirectoryEventURL( const char* url ); const char* getContentDirectoryEventURL() const; void setContentDirectoryControlURL( const char* url ); const char* getContentDirectoryControlURL() const; void subscribeToContentDirectory(); void fetchContents(); void setPlaylistNode( playlist_item_t* node ); bool compareSID( const char* sid ); private: bool _fetchContents( Container* parent ); void _buildPlaylist( Container* container ); IXML_Document* _browseAction( const char*, const char*, const char*, const char*, const char*, const char* ); Cookie* _cookie; Container* _contents; playlist_item_t* _playlistNode; std::string _UDN; std::string _friendlyName; std::string _contentDirectoryEventURL; std::string _contentDirectoryControlURL; int _subscriptionTimeOut; Upnp_SID _subscriptionID; }; class MediaServerList { public: MediaServerList( Cookie* cookie ); ~MediaServerList(); bool addServer( MediaServer* s ); void removeServer( const char* UDN ); MediaServer* getServer( const char* UDN ); MediaServer* getServerBySID( const char* ); private: Cookie* _cookie; std::vector _list; }; class Item { public: Item( Container* parent, const char* objectID, const char* title, const char* resource ); const char* getObjectID() const; const char* getTitle() const; const char* getResource() const; void setPlaylistNode( playlist_item_t* node ); playlist_item_t* getPlaylistNode() const ; private: playlist_item_t* _playlistNode; Container* _parent; std::string _objectID; std::string _title; std::string _resource; }; class Container { public: Container( Container* parent, const char* objectID, const char* title ); ~Container(); void addItem( Item* item ); void addContainer( Container* container ); const char* getObjectID() const; const char* getTitle() const; unsigned int getNumItems() const; unsigned int getNumContainers() const; Item* getItem( unsigned int i ) const; Container* getContainer( unsigned int i ) const; void setPlaylistNode( playlist_item_t* node ); playlist_item_t* getPlaylistNode() const; private: playlist_item_t* _playlistNode; Container* _parent; std::string _objectID; std::string _title; std::vector _items; std::vector _containers; }; // VLC callback prototypes static int Open( vlc_object_t* ); static void Close( vlc_object_t* ); static void Run( services_discovery_t *p_sd ); // Module descriptor vlc_module_begin(); set_shortname( "UPnP" ); set_description( _( "Universal Plug'n'Play discovery ( Intel SDK )" ) ); set_category( CAT_PLAYLIST ); set_subcategory( SUBCAT_PLAYLIST_SD ); set_capability( "services_discovery", 0 ); set_callbacks( Open, Close ); vlc_module_end(); // More prototypes... static Lockable* CallbackLock; static int Callback( Upnp_EventType eventType, void* event, void* pCookie ); const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName ); IXML_Document* parseBrowseResult( IXML_Document* doc ); // VLC callbacks... static int Open( vlc_object_t *p_this ) { services_discovery_t *p_sd = ( services_discovery_t* )p_this; services_discovery_sys_t *p_sys = ( services_discovery_sys_t * ) malloc( sizeof( services_discovery_sys_t ) ); p_sd->pf_run = Run; p_sd->p_sys = p_sys; /* Create our playlist node */ playlist_NodesPairCreate( pl_Get( p_sd ), _("Devices"), &p_sys->p_node_cat, &p_sys->p_node_one, true ); return VLC_SUCCESS; } static void Close( vlc_object_t *p_this ) { services_discovery_t *p_sd = ( services_discovery_t* )p_this; services_discovery_sys_t *p_sys = p_sd->p_sys; playlist_NodeDelete( pl_Get( p_sd ), p_sys->p_node_one, true, true ); playlist_NodeDelete( pl_Get( p_sd ), p_sys->p_node_cat, true, true ); free( p_sys ); } static void Run( services_discovery_t* p_sd ) { int res; res = UpnpInit( 0, 0 ); if( res != UPNP_E_SUCCESS ) { msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) ); return; } Cookie cookie; cookie.serviceDiscovery = p_sd; cookie.serverList = new MediaServerList( &cookie ); CallbackLock = new Lockable( &cookie ); res = UpnpRegisterClient( Callback, &cookie, &cookie.clientHandle ); if( res != UPNP_E_SUCCESS ) { msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) ); goto shutDown; } res = UpnpSearchAsync( cookie.clientHandle, 5, MEDIA_SERVER_DEVICE_TYPE, &cookie ); if( res != UPNP_E_SUCCESS ) { msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) ); goto shutDown; } msg_Dbg( p_sd, "UPnP discovery started" ); while( !p_sd->b_die ) { msleep( 500 ); } msg_Dbg( p_sd, "UPnP discovery stopped" ); shutDown: UpnpFinish(); delete cookie.serverList; delete CallbackLock; } // XML utility functions: // Returns the value of a child element, or 0 on error const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName ) { if ( !parent ) return 0; if ( !tagName ) return 0; char* s = strdup( tagName ); IXML_NodeList* nodeList = ixmlElement_getElementsByTagName( parent, s ); free( s ); if ( !nodeList ) return 0; IXML_Node* element = ixmlNodeList_item( nodeList, 0 ); ixmlNodeList_free( nodeList ); if ( !element ) return 0; IXML_Node* textNode = ixmlNode_getFirstChild( element ); if ( !textNode ) return 0; return ixmlNode_getNodeValue( textNode ); } // Extracts the result document from a SOAP response IXML_Document* parseBrowseResult( IXML_Document* doc ) { if ( !doc ) return 0; IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc, "Result" ); if ( !resultList ) return 0; IXML_Node* resultNode = ixmlNodeList_item( resultList, 0 ); ixmlNodeList_free( resultList ); if ( !resultNode ) return 0; IXML_Node* textNode = ixmlNode_getFirstChild( resultNode ); if ( !textNode ) return 0; const char* resultString = ixmlNode_getNodeValue( textNode ); char* resultXML = strdup( resultString ); IXML_Document* browseDoc = ixmlParseBuffer( resultXML ); free( resultXML ); return browseDoc; } // Handles all UPnP events static int Callback( Upnp_EventType eventType, void* event, void* pCookie ) { Locker locker( CallbackLock ); Cookie* cookie = ( Cookie* )pCookie; switch( eventType ) { case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE: case UPNP_DISCOVERY_SEARCH_RESULT: { struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event; IXML_Document *descriptionDoc = 0; int res; res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc ); if ( res != UPNP_E_SUCCESS ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: Could not download device description!", __FILE__, __LINE__ ); return res; } MediaServer::parseDeviceDescription( descriptionDoc, discovery->Location, cookie ); ixmlDocument_free( descriptionDoc ); } break; case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE: { struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event; cookie->serverList->removeServer( discovery->DeviceId ); } break; case UPNP_EVENT_RECEIVED: { Upnp_Event* e = ( Upnp_Event* )event; MediaServer* server = cookie->serverList->getServerBySID( e->Sid ); if ( server ) server->fetchContents(); } break; case UPNP_EVENT_AUTORENEWAL_FAILED: case UPNP_EVENT_SUBSCRIPTION_EXPIRED: { // Re-subscribe... Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event; MediaServer* server = cookie->serverList->getServerBySID( s->Sid ); if ( server ) server->subscribeToContentDirectory(); } break; case UPNP_EVENT_SUBSCRIBE_COMPLETE: msg_Warn( cookie->serviceDiscovery, "subscription complete" ); break; case UPNP_DISCOVERY_SEARCH_TIMEOUT: msg_Warn( cookie->serviceDiscovery, "search timeout" ); break; default: msg_Dbg( cookie->serviceDiscovery, "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )", __FILE__, __LINE__, eventType ); break; } return UPNP_E_SUCCESS; } // Class implementations... // MediaServer... void MediaServer::parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie ) { if ( !doc ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; } if ( !location ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; } const char* baseURL = location; // Try to extract baseURL IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" ); if ( urlList ) { if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) ) { IXML_Node* textNode = ixmlNode_getFirstChild( urlNode ); if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode ); } ixmlNodeList_free( urlList ); } // Get devices IXML_NodeList* deviceList = ixmlDocument_getElementsByTagName( doc, "device" ); if ( deviceList ) { for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ ) { IXML_Element* deviceElement = ( IXML_Element* )ixmlNodeList_item( deviceList, i ); const char* deviceType = xml_getChildElementValue( deviceElement, "deviceType" ); if ( !deviceType ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no deviceType!", __FILE__, __LINE__ ); continue; } if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 ) continue; const char* UDN = xml_getChildElementValue( deviceElement, "UDN" ); if ( !UDN ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no UDN!", __FILE__, __LINE__ ); continue; } if ( cookie->serverList->getServer( UDN ) != 0 ) continue; const char* friendlyName = xml_getChildElementValue( deviceElement, "friendlyName" ); if ( !friendlyName ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no friendlyName!", __FILE__, __LINE__ ); continue; } MediaServer* server = new MediaServer( UDN, friendlyName, cookie ); if ( !cookie->serverList->addServer( server ) ) { delete server; server = 0; continue; } // Check for ContentDirectory service... IXML_NodeList* serviceList = ixmlElement_getElementsByTagName( deviceElement, "service" ); if ( serviceList ) { for ( unsigned int j = 0; j < ixmlNodeList_length( serviceList ); j++ ) { IXML_Element* serviceElement = ( IXML_Element* )ixmlNodeList_item( serviceList, j ); const char* serviceType = xml_getChildElementValue( serviceElement, "serviceType" ); if ( !serviceType ) continue; if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE, serviceType ) != 0 ) continue; const char* eventSubURL = xml_getChildElementValue( serviceElement, "eventSubURL" ); if ( !eventSubURL ) continue; const char* controlURL = xml_getChildElementValue( serviceElement, "controlURL" ); if ( !controlURL ) continue; // Try to subscribe to ContentDirectory service char* url = ( char* )malloc( strlen( baseURL ) + strlen( eventSubURL ) + 1 ); if ( url ) { char* s1 = strdup( baseURL ); char* s2 = strdup( eventSubURL ); if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS ) { // msg_Dbg( cookie->serviceDiscovery, "CDS EVENT URL: %s", url ); server->setContentDirectoryEventURL( url ); server->subscribeToContentDirectory(); } free( s1 ); free( s2 ); free( url ); } // Try to browse content directory... url = ( char* )malloc( strlen( baseURL ) + strlen( controlURL ) + 1 ); if ( url ) { char* s1 = strdup( baseURL ); char* s2 = strdup( controlURL ); if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS ) { // msg_Dbg( cookie->serviceDiscovery, "CDS CTRL URL: %s", url ); server->setContentDirectoryControlURL( url ); server->fetchContents(); } free( s1 ); free( s2 ); free( url ); } } ixmlNodeList_free( serviceList ); } } ixmlNodeList_free( deviceList ); } } MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie ) { _cookie = cookie; _UDN = UDN; _friendlyName = friendlyName; _contents = 0; _playlistNode = 0; } MediaServer::~MediaServer() { if ( _contents ) { playlist_NodeDelete( pl_Get( _cookie->serviceDiscovery ) , _playlistNode, true, true ); } delete _contents; } const char* MediaServer::getUDN() const { const char* s = _UDN.c_str(); return s; } const char* MediaServer::getFriendlyName() const { const char* s = _friendlyName.c_str(); return s; } void MediaServer::setContentDirectoryEventURL( const char* url ) { _contentDirectoryEventURL = url; } const char* MediaServer::getContentDirectoryEventURL() const { const char* s = _contentDirectoryEventURL.c_str(); return s; } void MediaServer::setContentDirectoryControlURL( const char* url ) { _contentDirectoryControlURL = url; } const char* MediaServer::getContentDirectoryControlURL() const { return _contentDirectoryControlURL.c_str(); } void MediaServer::subscribeToContentDirectory() { const char* url = getContentDirectoryEventURL(); if ( !url || strcmp( url, "" ) == 0 ) { msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" ); return; } int timeOut = 1810; Upnp_SID sid; int res = UpnpSubscribe( _cookie->clientHandle, url, &timeOut, sid ); if ( res == UPNP_E_SUCCESS ) { _subscriptionTimeOut = timeOut; memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) ); } else { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__, getFriendlyName(), UpnpGetErrorMessage( res ) ); } } IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pBrowseFlag, const char* pFilter, const char* pStartingIndex, const char* pRequestedCount, const char* pSortCriteria ) { IXML_Document* action = 0; IXML_Document* response = 0; const char* url = getContentDirectoryControlURL(); if ( !url || strcmp( url, "" ) == 0 ) { msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" ); return 0; } char* ObjectID = strdup( pObjectID ); char* BrowseFlag = strdup( pBrowseFlag ); char* Filter = strdup( pFilter ); char* StartingIndex = strdup( pStartingIndex ); char* RequestedCount = strdup( pRequestedCount ); char* SortCriteria = strdup( pSortCriteria ); char* serviceType = strdup( CONTENT_DIRECTORY_SERVICE_TYPE ); int res; res = UpnpAddToAction( &action, "Browse", serviceType, "ObjectID", ObjectID ); if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; } res = UpnpAddToAction( &action, "Browse", serviceType, "BrowseFlag", BrowseFlag ); if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; } res = UpnpAddToAction( &action, "Browse", serviceType, "Filter", Filter ); if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; } res = UpnpAddToAction( &action, "Browse", serviceType, "StartingIndex", StartingIndex ); if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; } res = UpnpAddToAction( &action, "Browse", serviceType, "RequestedCount", RequestedCount ); if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; } res = UpnpAddToAction( &action, "Browse", serviceType, "SortCriteria", SortCriteria ); if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; } res = UpnpSendAction( _cookie->clientHandle, url, CONTENT_DIRECTORY_SERVICE_TYPE, 0, action, &response ); if ( res != UPNP_E_SUCCESS ) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); ixmlDocument_free( response ); response = 0; } browseActionCleanup: free( ObjectID ); free( BrowseFlag ); free( Filter ); free( StartingIndex ); free( RequestedCount ); free( SortCriteria ); free( serviceType ); ixmlDocument_free( action ); return response; } void MediaServer::fetchContents() { Container* root = new Container( 0, "0", getFriendlyName() ); playlist_t * p_playlist = pl_Get( _cookie->serviceDiscovery ); _fetchContents( root ); if ( _contents ) { PL_LOCK; playlist_NodeEmpty( p_playlist, _playlistNode, true ); PL_UNLOCK; delete _contents; } _contents = root; _contents->setPlaylistNode( _playlistNode ); _buildPlaylist( _contents ); } bool MediaServer::_fetchContents( Container* parent ) { if (!parent) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: parent==NULL", __FILE__, __LINE__ ); return false; } IXML_Document* response = _browseAction( parent->getObjectID(), "BrowseDirectChildren", "*", "0", "0", "" ); if ( !response ) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR!", __FILE__, __LINE__ ); return false; } IXML_Document* result = parseBrowseResult( response ); ixmlDocument_free( response ); if ( !result ) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR!", __FILE__, __LINE__ ); return false; } IXML_NodeList* containerNodeList = ixmlDocument_getElementsByTagName( result, "container" ); if ( containerNodeList ) { for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ ) { IXML_Element* containerElement = ( IXML_Element* )ixmlNodeList_item( containerNodeList, i ); const char* objectID = ixmlElement_getAttribute( containerElement, "id" ); if ( !objectID ) continue; const char* childCountStr = ixmlElement_getAttribute( containerElement, "childCount" ); if ( !childCountStr ) continue; int childCount = atoi( childCountStr ); const char* title = xml_getChildElementValue( containerElement, "dc:title" ); if ( !title ) continue; const char* resource = xml_getChildElementValue( containerElement, "res" ); if ( resource && childCount < 1 ) { Item* item = new Item( parent, objectID, title, resource ); parent->addItem( item ); } else { Container* container = new Container( parent, objectID, title ); parent->addContainer( container ); if ( childCount > 0 ) _fetchContents( container ); } } ixmlNodeList_free( containerNodeList ); } IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result, "item" ); if ( itemNodeList ) { for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ ) { IXML_Element* itemElement = ( IXML_Element* )ixmlNodeList_item( itemNodeList, i ); const char* objectID = ixmlElement_getAttribute( itemElement, "id" ); if ( !objectID ) continue; const char* title = xml_getChildElementValue( itemElement, "dc:title" ); if ( !title ) continue; const char* resource = xml_getChildElementValue( itemElement, "res" ); if ( !resource ) continue; Item* item = new Item( parent, objectID, title, resource ); parent->addItem( item ); } ixmlNodeList_free( itemNodeList ); } ixmlDocument_free( result ); return true; } void MediaServer::_buildPlaylist( Container* parent ) { playlist_t *p_playlist = pl_Get( _cookie->serviceDiscovery ); for ( unsigned int i = 0; i < parent->getNumContainers(); i++ ) { Container* container = parent->getContainer( i ); playlist_item_t* parentNode = parent->getPlaylistNode(); char* title = strdup( container->getTitle() ); playlist_item_t* node = playlist_NodeCreate( p_playlist, title, parentNode, 0, NULL ); free( title ); container->setPlaylistNode( node ); _buildPlaylist( container ); } for ( unsigned int i = 0; i < parent->getNumItems(); i++ ) { Item* item = parent->getItem( i ); playlist_item_t* parentNode = parent->getPlaylistNode(); input_item_t* p_input = input_ItemNew( _cookie->serviceDiscovery, item->getResource(), item->getTitle() ); int i_cat; /* FIXME: playlist_AddInput() can fail */ playlist_BothAddInput( p_playlist, p_input, parentNode, PLAYLIST_APPEND, PLAYLIST_END, &i_cat, NULL, false ); vlc_gc_decref( p_input ); /* TODO: do this better by storing ids */ playlist_item_t *p_node = playlist_ItemGetById( p_playlist, i_cat, false ); assert( p_node ); item->setPlaylistNode( p_node ); } } void MediaServer::setPlaylistNode( playlist_item_t* playlistNode ) { _playlistNode = playlistNode; } bool MediaServer::compareSID( const char* sid ) { return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 ); } // MediaServerList... MediaServerList::MediaServerList( Cookie* cookie ) { _cookie = cookie; } MediaServerList::~MediaServerList() { for ( unsigned int i = 0; i < _list.size(); i++ ) { delete _list[i]; } } bool MediaServerList::addServer( MediaServer* s ) { if ( getServer( s->getUDN() ) != 0 ) return false; msg_Dbg( _cookie->serviceDiscovery, "Adding server '%s'", s->getFriendlyName() ); _list.push_back( s ); char* name = strdup( s->getFriendlyName() ); playlist_item_t* node = playlist_NodeCreate( pl_Get( _cookie->serviceDiscovery ), name, _cookie->serviceDiscovery->p_sys->p_node_cat, 0, NULL ); free( name ); s->setPlaylistNode( node ); return true; } MediaServer* MediaServerList::getServer( const char* UDN ) { MediaServer* result = 0; for ( unsigned int i = 0; i < _list.size(); i++ ) { if( strcmp( UDN, _list[i]->getUDN() ) == 0 ) { result = _list[i]; break; } } return result; } MediaServer* MediaServerList::getServerBySID( const char* sid ) { MediaServer* server = 0; for ( unsigned int i = 0; i < _list.size(); i++ ) { if ( _list[i]->compareSID( sid ) ) { server = _list[i]; break; } } return server; } void MediaServerList::removeServer( const char* UDN ) { MediaServer* server = getServer( UDN ); if ( !server ) return; msg_Dbg( _cookie->serviceDiscovery, "Removing server '%s'", server->getFriendlyName() ); std::vector::iterator it; for ( it = _list.begin(); it != _list.end(); it++ ) { if ( *it == server ) { _list.erase( it ); delete server; break; } } } // Item... Item::Item( Container* parent, const char* objectID, const char* title, const char* resource ) { _parent = parent; _objectID = objectID; _title = title; _resource = resource; _playlistNode = 0; } const char* Item::getObjectID() const { return _objectID.c_str(); } const char* Item::getTitle() const { return _title.c_str(); } const char* Item::getResource() const { return _resource.c_str(); } void Item::setPlaylistNode( playlist_item_t* node ) { _playlistNode = node; } playlist_item_t* Item::getPlaylistNode() const { return _playlistNode; } // Container... Container::Container( Container* parent, const char* objectID, const char* title ) { _parent = parent; _objectID = objectID; _title = title; _playlistNode = 0; } Container::~Container() { for ( unsigned int i = 0; i < _containers.size(); i++ ) { delete _containers[i]; } for ( unsigned int i = 0; i < _items.size(); i++ ) { delete _items[i]; } } void Container::addItem( Item* item ) { _items.push_back( item ); } void Container::addContainer( Container* container ) { _containers.push_back( container ); } const char* Container::getObjectID() const { return _objectID.c_str(); } const char* Container::getTitle() const { return _title.c_str(); } unsigned int Container::getNumItems() const { return _items.size(); } unsigned int Container::getNumContainers() const { return _containers.size(); } Item* Container::getItem( unsigned int i ) const { if ( i < _items.size() ) return _items[i]; return 0; } Container* Container::getContainer( unsigned int i ) const { if ( i < _containers.size() ) return _containers[i]; return 0; } void Container::setPlaylistNode( playlist_item_t* node ) { _playlistNode = node; } playlist_item_t* Container::getPlaylistNode() const { return _playlistNode; }