You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1092 lines
29 KiB
1092 lines
29 KiB
/*****************************************************************************
|
|
* Upnp_intell.cpp : UPnP discovery module (Intel SDK)
|
|
*****************************************************************************
|
|
* Copyright (C) 2004-2006 the VideoLAN team
|
|
* $Id$
|
|
*
|
|
* Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin)
|
|
* Christian Henz <henz # c-lab.de>
|
|
*
|
|
* 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 ???
|
|
*/
|
|
|
|
|
|
#include <vector>
|
|
#include <string>
|
|
|
|
#include <upnp/upnp.h>
|
|
#include <upnp/upnptools.h>
|
|
|
|
#undef PACKAGE_NAME
|
|
#include <vlc/vlc.h>
|
|
#include <vlc_playlist.h>
|
|
#include "vlc_strings.h"
|
|
|
|
|
|
// 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( c->serviceDiscovery, &_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<MediaServer*> _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<Item*> _items;
|
|
std::vector<Container*> _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,
|
|
VLC_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, VLC_TRUE,
|
|
VLC_TRUE );
|
|
playlist_NodeDelete( pl_Get( p_sd ), p_sys->p_node_cat, VLC_TRUE,
|
|
VLC_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 );
|
|
|
|
resolve_xml_special_chars( resultXML );
|
|
|
|
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, VLC_TRUE, VLC_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, VLC_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 );
|
|
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;
|
|
playlist_BothAddInput( p_playlist, p_input, parentNode,
|
|
PLAYLIST_APPEND, PLAYLIST_END, &i_cat, NULL,
|
|
VLC_FALSE );
|
|
/* TODO: do this better by storing ids */
|
|
playlist_item_t *p_node = playlist_ItemGetById( p_playlist, i_cat, VLC_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 );
|
|
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<MediaServer*>::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;
|
|
}
|
|
|