[vlc-devel] [PATCH] [WIP] Partial UPnP rewrite
Tristan Matthews
tmatth at videolan.org
Tue Mar 10 19:25:04 CET 2015
On Tue, Mar 10, 2015 at 1:59 PM, Hugo Beauzée-Luyssen <hugo at beauzee.fr> wrote:
> This is splitting the UPnP module upon 2 parts:
> - A service discovery module that is solely responsible for discovering
> UPnP devices on the network
> - An access module, that will leverage the recently introduced
> pf_readdir calback to list directories.
>
> This removes the need for recursion and handling of all the items from
> withing the SD module.
> It hasn't been tested thoroughly yet, though all comments are more than
> welcome.
> ---
> modules/services_discovery/upnp.cpp | 1229 ++++++++++++-----------------------
> modules/services_discovery/upnp.hpp | 181 ++----
> 2 files changed, 487 insertions(+), 923 deletions(-)
>
> diff --git a/modules/services_discovery/upnp.cpp b/modules/services_discovery/upnp.cpp
> index ab765aa..4f252cc 100644
> --- a/modules/services_discovery/upnp.cpp
> +++ b/modules/services_discovery/upnp.cpp
> @@ -7,6 +7,7 @@
> * Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin)
> * Christian Henz <henz # c-lab.de>
> * Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
> + * Hugo Beauzée-Luyssen <hugo at beauzee.fr>
> *
> * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
> *
> @@ -32,13 +33,16 @@
> # include "config.h"
> #endif
>
> -#include "services_discovery/upnp.hpp"
> +#include "upnp.hpp"
>
> +#include <vlc_access.h>
> #include <vlc_plugin.h>
> #include <vlc_services_discovery.h>
> +#include <vlc_url.h>
>
> #include <assert.h>
> #include <limits.h>
> +#include <algorithm>
>
> /*
> * Constants
> @@ -51,139 +55,63 @@ const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:Conte
> */
> struct services_discovery_sys_t
> {
> - UpnpClient_Handle client_handle;
> - MediaServerList* p_server_list;
> - vlc_mutex_t callback_lock;
> + SD::MediaServerList* p_server_list;
> + UpnpInstanceWrapper* p_upnp;
> };
>
> +struct access_sys_t
> +{
> + UpnpInstanceWrapper* p_upnp;
> +};
> +
> +UpnpInstanceWrapper* UpnpInstanceWrapper::s_instance;
> +vlc_mutex_t UpnpInstanceWrapper::s_lock = VLC_STATIC_MUTEX;
> +
> /*
> * VLC callback prototypes
> */
> -static int Open( vlc_object_t* );
> -static void Close( vlc_object_t* );
> +namespace SD
> +{
> + static int Open( vlc_object_t* );
> + static void Close( vlc_object_t* );
> +}
> +
> +namespace Access
> +{
> + static int Open( vlc_object_t* );
> + static void Close( vlc_object_t* );
> +}
> +
> VLC_SD_PROBE_HELPER( "upnp", "Universal Plug'n'Play", SD_CAT_LAN )
>
> /*
> * Module descriptor
> */
> -vlc_module_begin();
> +vlc_module_begin()
> set_shortname( "UPnP" );
> set_description( N_( "Universal Plug'n'Play" ) );
> set_category( CAT_PLAYLIST );
> set_subcategory( SUBCAT_PLAYLIST_SD );
> set_capability( "services_discovery", 0 );
> - set_callbacks( Open, Close );
> + set_callbacks( SD::Open, SD::Close );
> +
> + add_submodule()
> + set_category( CAT_INPUT )
> + set_subcategory( SUBCAT_INPUT_ACCESS )
> + set_callbacks( Access::Open, Access::Close )
> + set_capability( "access", 0 )
>
> VLC_SD_PROBE_SUBMODULE
> -vlc_module_end();
> +vlc_module_end()
>
> -/*
> - * Local prototypes
> - */
> -static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data );
>
> +/* XML utility functions */
> const char* xml_getChildElementValue( IXML_Element* p_parent,
> const char* psz_tag_name );
>
> const char* xml_getChildElementValue( IXML_Document* p_doc,
> const char* psz_tag_name );
>
> -const char* xml_getChildElementAttributeValue( IXML_Element* p_parent,
> - const char* psz_tag_name,
> - const char* psz_attribute );
> -
> -int xml_getNumber( IXML_Document* p_doc,
> - const char* psz_tag_name );
> -
> -IXML_Document* parseBrowseResult( IXML_Document* p_doc );
> -
> -/*
> - * Initializes UPNP instance.
> - */
> -static int Open( vlc_object_t *p_this )
> -{
> - int i_res;
> - services_discovery_t *p_sd = ( services_discovery_t* )p_this;
> - services_discovery_sys_t *p_sys = ( services_discovery_sys_t * )
> - calloc( 1, sizeof( services_discovery_sys_t ) );
> -
> - if( !( p_sd->p_sys = p_sys ) )
> - return VLC_ENOMEM;
> -
> -#ifdef UPNP_ENABLE_IPV6
> - char* psz_miface;
> - psz_miface = var_InheritString( p_sd, "miface" );
> - msg_Info( p_sd, "Initializing libupnp on '%s' interface", psz_miface );
> - i_res = UpnpInit2( psz_miface, 0 );
> - free( psz_miface );
> -#else
> - /* If UpnpInit2 isnt available, initialize on first IPv4-capable interface */
> - i_res = UpnpInit( 0, 0 );
> -#endif
> - if( i_res != UPNP_E_SUCCESS )
> - {
> - msg_Err( p_sd, "Initialization failed: %s", UpnpGetErrorMessage( i_res ) );
> - free( p_sys );
> - return VLC_EGENERIC;
> - }
> -
> - ixmlRelaxParser( 1 );
> -
> - p_sys->p_server_list = new MediaServerList( p_sd );
> - vlc_mutex_init( &p_sys->callback_lock );
> -
> - /* Register a control point */
> - i_res = UpnpRegisterClient( Callback, p_sd, &p_sys->client_handle );
> - if( i_res != UPNP_E_SUCCESS )
> - {
> - msg_Err( p_sd, "Client registration failed: %s", UpnpGetErrorMessage( i_res ) );
> - Close( (vlc_object_t*) p_sd );
> - return VLC_EGENERIC;
> - }
> -
> - /* Search for media servers */
> - i_res = UpnpSearchAsync( p_sys->client_handle, 5,
> - MEDIA_SERVER_DEVICE_TYPE, p_sd );
> - if( i_res != UPNP_E_SUCCESS )
> - {
> - msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
> - Close( (vlc_object_t*) p_sd );
> - return VLC_EGENERIC;
> - }
> -
> - /* libupnp does not treat a maximum content length of 0 as unlimited
> - * until 64dedf (~ pupnp v1.6.7) and provides no sane way to discriminate
> - * between versions */
> - if( (i_res = UpnpSetMaxContentLength( INT_MAX )) != UPNP_E_SUCCESS )
> - {
> - msg_Err( p_sd, "Failed to set maximum content length: %s",
> - UpnpGetErrorMessage( i_res ));
> -
> - Close( (vlc_object_t*) p_sd );
> - return VLC_EGENERIC;
> - }
> -
> - return VLC_SUCCESS;
> -}
> -
> -/*
> - * Releases resources.
> - */
> -static void Close( vlc_object_t *p_this )
> -{
> - services_discovery_t *p_sd = ( services_discovery_t* )p_this;
> -
> - UpnpUnRegisterClient( p_sd->p_sys->client_handle );
> - UpnpFinish();
> -
> - delete p_sd->p_sys->p_server_list;
> - vlc_mutex_destroy( &p_sd->p_sys->callback_lock );
> -
> - free( p_sd->p_sys );
> -}
> -
> -/* XML utility functions */
> -
> /*
> * Returns the value of a child element, or NULL on error
> */
> @@ -208,28 +136,6 @@ const char* xml_getChildElementValue( IXML_Element* p_parent,
> }
>
> /*
> - * Returns the value of a child element's attribute, or NULL on error
> - */
> -const char* xml_getChildElementAttributeValue( IXML_Element* p_parent,
> - const char* psz_tag_name,
> - const char* psz_attribute )
> -{
> - assert( p_parent );
> - assert( psz_tag_name );
> - assert( psz_attribute );
> -
> - IXML_NodeList* p_node_list;
> - p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name );
> - if ( !p_node_list ) return NULL;
> -
> - IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
> - ixmlNodeList_free( p_node_list );
> - if ( !p_element ) return NULL;
> -
> - return ixmlElement_getAttribute( (IXML_Element*) p_element, psz_attribute );
> -}
> -
> -/*
> * Returns the value of a child element, or NULL on error
> */
> const char* xml_getChildElementValue( IXML_Document* p_doc,
> @@ -302,171 +208,173 @@ IXML_Document* parseBrowseResult( IXML_Document* p_doc )
> return (IXML_Document*)p_node;
> }
>
> +namespace SD
> +{
> +
> /*
> - * Get the number value from a SOAP response
> + * Initializes UPNP instance.
> */
> -int xml_getNumber( IXML_Document* p_doc,
> - const char* psz_tag_name )
> +static int Open( vlc_object_t *p_this )
> {
> - assert( p_doc );
> - assert( psz_tag_name );
> + services_discovery_t *p_sd = ( services_discovery_t* )p_this;
> + services_discovery_sys_t *p_sys = ( services_discovery_sys_t * )
> + calloc( 1, sizeof( services_discovery_sys_t ) );
>
> - const char* psz = xml_getChildElementValue( p_doc, psz_tag_name );
> + if( !( p_sd->p_sys = p_sys ) )
> + return VLC_ENOMEM;
>
> - if( !psz )
> - return 0;
> + p_sys->p_server_list = new(std::nothrow) SD::MediaServerList( p_sd );
> + if ( unlikely( p_sys->p_server_list == NULL ) )
> + {
> + return VLC_ENOMEM;
> + }
>
> - char *psz_end;
> - long l = strtol( psz, &psz_end, 10 );
> + p_sys->p_upnp = UpnpInstanceWrapper::get( p_this, SD::MediaServerList::Callback, p_sys->p_server_list );
> + if ( !p_sys->p_upnp )
> + {
> + Close( p_this );
> + return VLC_EGENERIC;
> + }
>
> - if( *psz_end || l < 0 || l > INT_MAX )
> - return 0;
> + /* Search for media servers */
> + int i_res = UpnpSearchAsync( p_sys->p_upnp->handle(), 5,
> + MEDIA_SERVER_DEVICE_TYPE, p_sys->p_server_list );
> + if( i_res != UPNP_E_SUCCESS )
> + {
> + msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
> + Close( (vlc_object_t*) p_sd );
Why not just Close( p_this ); like above?
> + return VLC_EGENERIC;
> + }
>
> - return (int)l;
> + return VLC_SUCCESS;
> }
>
> /*
> - * Handles all UPnP events
> + * Releases resources.
> */
> -static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data )
> +static void Close( vlc_object_t *p_this )
> {
> - services_discovery_t* p_sd = ( services_discovery_t* ) p_user_data;
> - services_discovery_sys_t* p_sys = p_sd->p_sys;
> - vlc_mutex_locker locker( &p_sys->callback_lock );
> -
> - switch( event_type )
> - {
> - case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
> - case UPNP_DISCOVERY_SEARCH_RESULT:
> - {
> - struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
> + services_discovery_t *p_sd = ( services_discovery_t* )p_this;
> + services_discovery_sys_t *p_sys = p_sd->p_sys;
>
> - IXML_Document *p_description_doc = 0;
> + if (p_sys->p_upnp)
> + p_sys->p_upnp->release( true );
> + delete p_sys->p_server_list;
> + free( p_sys );
> +}
>
> - int i_res;
> - i_res = UpnpDownloadXmlDoc( p_discovery->Location, &p_description_doc );
> - if ( i_res != UPNP_E_SUCCESS )
> - {
> - msg_Warn( p_sd, "Could not download device description! "
> - "Fetching data from %s failed: %s",
> - p_discovery->Location, UpnpGetErrorMessage( i_res ) );
> - return i_res;
> - }
> +MediaServerDesc::MediaServerDesc(const std::string& udn, const std::string& fName, const std::string& loc)
> + : UDN( udn )
> + , friendlyName( fName )
> + , location( loc )
> + , inputItem( NULL )
> +{
> +}
>
> - MediaServer::parseDeviceDescription( p_description_doc,
> - p_discovery->Location, p_sd );
> +MediaServerDesc::~MediaServerDesc()
> +{
> + if (inputItem)
> + vlc_gc_decref( inputItem );
> +}
>
> - ixmlDocument_free( p_description_doc );
> - }
> - break;
> +/*
> + * MediaServerList class
> + */
> +MediaServerList::MediaServerList( services_discovery_t* p_sd )
> + : _p_sd( p_sd )
> +{
> + vlc_mutex_init( &_lock );
> +}
>
> - case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
> - {
> - struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
> +MediaServerList::~MediaServerList()
> +{
> + vlc_delete_all(_list);
> + vlc_mutex_destroy( &_lock );
> +}
>
> - p_sys->p_server_list->removeServer( p_discovery->DeviceId );
> +bool MediaServerList::addServer( MediaServerDesc* desc )
> +{
> + vlc_mutex_locker lock( &_lock );
> + input_item_t* p_input_item = NULL;
> + if ( getServer( desc->UDN ) != 0 )
> + return false;
>
> - }
> - break;
> + msg_Dbg( _p_sd, "Adding server '%s' with uuid '%s'", desc->friendlyName.c_str(), desc->UDN.c_str() );
>
> - case UPNP_EVENT_RECEIVED:
> - {
> - Upnp_Event* p_e = ( Upnp_Event* )p_event;
> + char* psz_mrl;
> + if( asprintf(&psz_mrl, "upnp://%s?ObjectID=%s", desc->location.c_str(), desc->UDN.c_str() ) < 0 )
> + return false;
>
> - MediaServer* p_server = p_sys->p_server_list->getServerBySID( p_e->Sid );
> - if ( p_server ) p_server->fetchContents();
> - }
> - break;
> + p_input_item = input_item_NewWithType( psz_mrl, desc->friendlyName.c_str(), 0,
> + NULL, 0, -1, ITEM_TYPE_NODE );
> + free( psz_mrl );
> + if ( !p_input_item )
> + return false;
> + desc->inputItem = p_input_item;
> + input_item_SetDescription( p_input_item, desc->UDN.c_str() );
> + services_discovery_AddItem( _p_sd, p_input_item, NULL );
> + _list.push_back( desc );
> + return true;
> +}
>
> - case UPNP_EVENT_AUTORENEWAL_FAILED:
> - case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
> +MediaServerDesc* MediaServerList::getServer( const std::string& udn )
> +{
> + for ( unsigned int i = 0; i < _list.size(); i++ )
> {
> - /* Re-subscribe. */
> -
> - Upnp_Event_Subscribe* p_s = ( Upnp_Event_Subscribe* )p_event;
> -
> - MediaServer* p_server = p_sys->p_server_list->getServerBySID( p_s->Sid );
> - if ( p_server ) p_server->subscribeToContentDirectory();
> - }
> - break;
> -
> - case UPNP_EVENT_SUBSCRIBE_COMPLETE:
> - msg_Warn( p_sd, "subscription complete" );
> - break;
> -
> - case UPNP_DISCOVERY_SEARCH_TIMEOUT:
> - msg_Warn( p_sd, "search timeout" );
> - break;
> -
> - default:
> - msg_Err( p_sd, "Unhandled event, please report ( type=%d )", event_type );
> - break;
> + if( udn == _list[i]->UDN )
> + {
> + return _list[i];
> + }
> }
> -
> - return UPNP_E_SUCCESS;
> + return NULL;
> }
>
> -
> -/*
> - * Local class implementations.
> - */
> -
> -/*
> - * MediaServer
> - */
> -
> -void MediaServer::parseDeviceDescription( IXML_Document* p_doc,
> - const char* p_location,
> - services_discovery_t* p_sd )
> +void MediaServerList::parseNewServer( IXML_Document *doc, const std::string &location )
> {
> - if ( !p_doc )
> + if ( !doc )
> {
> - msg_Err( p_sd, "Null IXML_Document" );
> + msg_Err( _p_sd, "Null IXML_Document" );
> return;
> }
>
> - if ( !p_location )
> + if ( location.empty() )
> {
> - msg_Err( p_sd, "Null location" );
> + msg_Err( _p_sd, "Empty location" );
> return;
> }
>
> - const char* psz_base_url = p_location;
> + const char* psz_base_url = location.c_str();
>
> /* Try to extract baseURL */
> - IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( p_doc, "URLBase" );
> + IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( doc, "URLBase" );
> if ( p_url_list )
> {
> -
> if ( IXML_Node* p_url_node = ixmlNodeList_item( p_url_list, 0 ) )
> {
> IXML_Node* p_text_node = ixmlNode_getFirstChild( p_url_node );
> - if ( p_text_node ) psz_base_url = ixmlNode_getNodeValue( p_text_node );
> + if ( p_text_node )
> + psz_base_url = ixmlNode_getNodeValue( p_text_node );
> }
> -
> ixmlNodeList_free( p_url_list );
> }
>
> /* Get devices */
> - IXML_NodeList* p_device_list =
> - ixmlDocument_getElementsByTagName( p_doc, "device" );
> + IXML_NodeList* p_device_list = ixmlDocument_getElementsByTagName( doc, "device" );
>
> if ( p_device_list )
> {
> for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_list ); i++ )
> {
> - IXML_Element* p_device_element =
> - ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
> + IXML_Element* p_device_element = ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
>
> if( !p_device_element )
> continue;
>
> - const char* psz_device_type =
> - xml_getChildElementValue( p_device_element, "deviceType" );
> + const char* psz_device_type = xml_getChildElementValue( p_device_element, "deviceType" );
>
> if ( !psz_device_type )
> {
> - msg_Warn( p_sd, "No deviceType found!" );
> + msg_Warn( _p_sd, "No deviceType found!" );
> continue;
> }
>
> @@ -478,14 +386,14 @@ void MediaServer::parseDeviceDescription( IXML_Document* p_doc,
> "UDN" );
> if ( !psz_udn )
> {
> - msg_Warn( p_sd, "No UDN!" );
> + msg_Warn( _p_sd, "No UDN!" );
> continue;
> }
>
> /* Check if server is already added */
> - if ( p_sd->p_sys->p_server_list->getServer( psz_udn ) != 0 )
> + if ( _p_sd->p_sys->p_server_list->getServer( psz_udn ) != 0 )
> {
> - msg_Warn( p_sd, "Server with uuid '%s' already exists.", psz_udn );
> + msg_Warn( _p_sd, "Server with uuid '%s' already exists.", psz_udn );
> continue;
> }
>
> @@ -495,38 +403,25 @@ void MediaServer::parseDeviceDescription( IXML_Document* p_doc,
>
> if ( !psz_friendly_name )
> {
> - msg_Dbg( p_sd, "No friendlyName!" );
> + msg_Dbg( _p_sd, "No friendlyName!" );
> continue;
> }
>
> - MediaServer* p_server = new MediaServer( psz_udn,
> - psz_friendly_name, p_sd );
> -
> - if ( !p_sd->p_sys->p_server_list->addServer( p_server ) )
> - {
> - delete p_server;
> - p_server = 0;
> - continue;
> - }
> + // We now have basic info, we need to get the content browsing url
> + // so the access module can browse without fetching the manifest again
>
> /* Check for ContentDirectory service. */
> - IXML_NodeList* p_service_list =
> - ixmlElement_getElementsByTagName( p_device_element,
> - "service" );
> + IXML_NodeList* p_service_list = ixmlElement_getElementsByTagName( p_device_element, "service" );
> if ( p_service_list )
> {
> - for ( unsigned int j = 0;
> - j < ixmlNodeList_length( p_service_list ); j++ )
> + for ( unsigned int j = 0; j < ixmlNodeList_length( p_service_list ); j++ )
> {
> - IXML_Element* p_service_element =
> - ( IXML_Element* ) ixmlNodeList_item( p_service_list, j );
> + IXML_Element* p_service_element = (IXML_Element*)ixmlNodeList_item( p_service_list, j );
>
> - const char* psz_service_type =
> - xml_getChildElementValue( p_service_element,
> - "serviceType" );
> + const char* psz_service_type = xml_getChildElementValue( p_service_element, "serviceType" );
> if ( !psz_service_type )
> {
> - msg_Warn( p_sd, "No service type found." );
> + msg_Warn( _p_sd, "No service type found." );
> continue;
> }
>
> @@ -535,241 +430,256 @@ void MediaServer::parseDeviceDescription( IXML_Document* p_doc,
> psz_service_type, k ) != 0 )
> continue;
>
> - p_server->_i_content_directory_service_version =
> - psz_service_type[k];
> -
> - const char* psz_event_sub_url =
> - xml_getChildElementValue( p_service_element,
> - "eventSubURL" );
> - if ( !psz_event_sub_url )
> - {
> - msg_Warn( p_sd, "No event subscription url found." );
> - continue;
> - }
> -
> - const char* psz_control_url =
> - xml_getChildElementValue( p_service_element,
> + const char* psz_control_url = xml_getChildElementValue( p_service_element,
> "controlURL" );
> if ( !psz_control_url )
> {
> - msg_Warn( p_sd, "No control url found." );
> + msg_Warn( _p_sd, "No control url found." );
> continue;
> }
>
> - /* Try to subscribe to ContentDirectory service */
> -
> - char* psz_url = ( char* ) malloc( strlen( psz_base_url ) +
> - strlen( psz_event_sub_url ) + 1 );
> - if ( psz_url )
> - {
> - if ( UpnpResolveURL( psz_base_url, psz_event_sub_url, psz_url ) ==
> - UPNP_E_SUCCESS )
> - {
> - p_server->setContentDirectoryEventURL( psz_url );
> - p_server->subscribeToContentDirectory();
> - }
> -
> - free( psz_url );
> - }
> -
> /* Try to browse content directory. */
> -
> - psz_url = ( char* ) malloc( strlen( psz_base_url ) +
> + char* psz_url = ( char* ) malloc( strlen( psz_base_url ) +
It was already like this before, but in a separate patch perhaps this
function should error out if malloc fails.
> strlen( psz_control_url ) + 1 );
> if ( psz_url )
> {
> - if ( UpnpResolveURL( psz_base_url, psz_control_url, psz_url ) ==
> - UPNP_E_SUCCESS )
> + if ( UpnpResolveURL( psz_base_url, psz_control_url, psz_url ) == UPNP_E_SUCCESS )
> {
> - p_server->setContentDirectoryControlURL( psz_url );
> - p_server->fetchContents();
> + SD::MediaServerDesc* p_server = new SD::MediaServerDesc( psz_udn,
> + psz_friendly_name, psz_url );
Any reason for using nothrow elsewhere but not here?
> +
> + if ( !addServer( p_server ) )
> + {
> + delete p_server;
> + continue;
> + }
> }
>
> free( psz_url );
> }
> }
> ixmlNodeList_free( p_service_list );
> - }
> + }
> }
> ixmlNodeList_free( p_device_list );
> }
> }
>
> -MediaServer::MediaServer( const char* psz_udn,
> - const char* psz_friendly_name,
> - services_discovery_t* p_sd )
> +void MediaServerList::removeServer( const std::string& udn )
> {
> - _p_sd = p_sd;
> + vlc_mutex_locker lock( &_lock );
>
> - _UDN = psz_udn;
> - _friendly_name = psz_friendly_name;
> + MediaServerDesc* p_server = getServer( udn );
> + if ( !p_server )
> + return;
>
> - _p_contents = NULL;
> - _p_input_item = NULL;
> - _i_content_directory_service_version = 1;
> -}
> + msg_Dbg( _p_sd, "Removing server '%s'", p_server->friendlyName.c_str() );
>
> -MediaServer::~MediaServer()
> -{
> - delete _p_contents;
> -}
> + assert(p_server->inputItem);
> + services_discovery_RemoveItem( _p_sd, p_server->inputItem );
>
> -const char* MediaServer::getUDN() const
> -{
> - return _UDN.c_str();
> + std::vector<MediaServerDesc*>::iterator it = std::find(_list.begin(), _list.end(), p_server);
> + if (it != _list.end())
> + {
> + _list.erase( it );
> + }
> + delete p_server;
> }
>
> -const char* MediaServer::getFriendlyName() const
> +/*
> + * Handles servers listing UPnP events
> + */
> +int MediaServerList::Callback( Upnp_EventType event_type, void* p_event, void* p_user_data )
> {
> - return _friendly_name.c_str();
> -}
> + MediaServerList* self = static_cast<MediaServerList*>( p_user_data );
> + services_discovery_t* p_sd = self->_p_sd;
>
> -void MediaServer::setContentDirectoryEventURL( const char* psz_url )
> -{
> - _content_directory_event_url = psz_url;
> + switch( event_type )
> + {
> + case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
> + case UPNP_DISCOVERY_SEARCH_RESULT:
> + {
> + struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
> +
> + IXML_Document *p_description_doc = 0;
> +
> + int i_res;
> + i_res = UpnpDownloadXmlDoc( p_discovery->Location, &p_description_doc );
> + if ( i_res != UPNP_E_SUCCESS )
> + {
> + msg_Warn( p_sd, "Could not download device description! "
> + "Fetching data from %s failed: %s",
> + p_discovery->Location, UpnpGetErrorMessage( i_res ) );
> + return i_res;
> + }
> + self->parseNewServer( p_description_doc, p_discovery->Location );
> + ixmlDocument_free( p_description_doc );
> + }
> + break;
> +
> + case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
> + {
> + struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
> +
> + self->removeServer( p_discovery->DeviceId );
> +
> + }
> + break;
> +
> + case UPNP_EVENT_SUBSCRIBE_COMPLETE:
> + msg_Warn( p_sd, "subscription complete" );
> + break;
> +
> + case UPNP_DISCOVERY_SEARCH_TIMEOUT:
> + msg_Warn( p_sd, "search timeout" );
> + break;
> +
> + case UPNP_EVENT_RECEIVED:
> + case UPNP_EVENT_AUTORENEWAL_FAILED:
> + case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
> + // Those are for the access part
> + break;
> +
> + default:
> + msg_Err( p_sd, "Unhandled event, please report ( type=%d )", event_type );
> + break;
> + }
> +
> + return UPNP_E_SUCCESS;
> }
>
> -const char* MediaServer::getContentDirectoryEventURL() const
> -{
> - return _content_directory_event_url.c_str();
> }
>
> -void MediaServer::setContentDirectoryControlURL( const char* psz_url )
> +namespace Access
> {
> - _content_directory_control_url = psz_url;
> -}
>
> -const char* MediaServer::getContentDirectoryControlURL() const
> +MediaServer::MediaServer(const char *psz_url, access_t *p_access, input_item_node_t *node)
> + : _url( psz_url )
> + , _access( p_access )
> + , _node( node )
> {
> - return _content_directory_control_url.c_str();
> }
>
> -/**
> - * Subscribes current client handle to Content Directory Service.
> - * CDS exports the server shares to clients.
> - */
> -void MediaServer::subscribeToContentDirectory()
> +void MediaServer::addItem(const char *objectID, const char *title )
> {
> - const char* psz_url = getContentDirectoryEventURL();
> - if ( !psz_url )
> + vlc_url_t url;
> + vlc_UrlParse( &url, _url.c_str(), '?' );
> + char* psz_url;
> +
> + if (asprintf( &psz_url, "upnp://%s://%s:%u%s?ObjectID=%s", url.psz_protocol,
> + url.psz_host, url.i_port ? url.i_port : 80, url.psz_path, objectID ) < 0 )
> {
> - msg_Dbg( _p_sd, "No subscription url set!" );
> - return;
> + vlc_UrlClean( &url );
> + return ;
> }
> + vlc_UrlClean( &url );
>
> - int i_timeout = 1810;
> - Upnp_SID sid;
> -
> - int i_res = UpnpSubscribe( _p_sd->p_sys->client_handle, psz_url, &i_timeout, sid );
> + input_item_t* p_item = input_item_NewWithType( psz_url, title, 0, NULL,
> + 0, -1, ITEM_TYPE_NODE );
> + free( psz_url);
> + if ( !p_item )
> + return;
> + input_item_CopyOptions( _node->p_item, p_item );
> + input_item_node_AppendItem( _node, p_item );
> + input_item_Release( p_item );
> +}
>
> - if ( i_res == UPNP_E_SUCCESS )
> - {
> - _i_subscription_timeout = i_timeout;
> - memcpy( _subscription_id, sid, sizeof( Upnp_SID ) );
> - }
> - else
> - {
> - msg_Dbg( _p_sd, "Subscribe failed: '%s': %s",
> - getFriendlyName(), UpnpGetErrorMessage( i_res ) );
> - }
> +void MediaServer::addItem(const char* title, const char*, const char*,
> + mtime_t duration, const char* psz_url)
> +{
> + input_item_t* p_item = input_item_NewExt( psz_url, title, 0, NULL, 0, duration );
> + input_item_node_AppendItem( _node, p_item );
> + input_item_Release( p_item );
> }
> -/*
> - * Constructs UpnpAction to browse available content.
> - */
> +
> +/* Access part */
> IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
> const char* psz_browser_flag_,
> const char* psz_filter_,
> - const char* psz_starting_index_,
> const char* psz_requested_count_,
> const char* psz_sort_criteria_ )
> {
> IXML_Document* p_action = 0;
> IXML_Document* p_response = 0;
> - const char* psz_url = getContentDirectoryControlURL();
> + const char* psz_url = _url.c_str();
>
> - if ( !psz_url )
> + if ( _url.empty() )
> {
> - msg_Dbg( _p_sd, "No subscription url set!" );
> + msg_Dbg( _access, "No subscription url set!" );
> return 0;
> }
>
> - char* psz_service_type = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
> -
> - psz_service_type[strlen( psz_service_type ) - 1] =
> - _i_content_directory_service_version;
> -
> int i_res;
>
> i_res = UpnpAddToAction( &p_action, "Browse",
> - psz_service_type, "ObjectID", psz_object_id_ );
> + CONTENT_DIRECTORY_SERVICE_TYPE, "ObjectID", psz_object_id_ );
>
> if ( i_res != UPNP_E_SUCCESS )
> {
> - msg_Dbg( _p_sd, "AddToAction 'ObjectID' failed: %s",
> + msg_Dbg( _access, "AddToAction 'ObjectID' failed: %s",
> UpnpGetErrorMessage( i_res ) );
> goto browseActionCleanup;
> }
>
> i_res = UpnpAddToAction( &p_action, "Browse",
> - psz_service_type, "BrowseFlag", psz_browser_flag_ );
> -
> + CONTENT_DIRECTORY_SERVICE_TYPE, "StartingIndex", "0" );
> if ( i_res != UPNP_E_SUCCESS )
> {
> - msg_Dbg( _p_sd, "AddToAction 'BrowseFlag' failed: %s",
> + msg_Dbg( _access, "AddToAction 'StartingIndex' failed: %s",
> UpnpGetErrorMessage( i_res ) );
> goto browseActionCleanup;
> }
>
> i_res = UpnpAddToAction( &p_action, "Browse",
> - psz_service_type, "Filter", psz_filter_ );
> + CONTENT_DIRECTORY_SERVICE_TYPE, "BrowseFlag", psz_browser_flag_ );
>
> if ( i_res != UPNP_E_SUCCESS )
> {
> - msg_Dbg( _p_sd, "AddToAction 'Filter' failed: %s",
> + msg_Dbg( _access, "AddToAction 'BrowseFlag' failed: %s",
> UpnpGetErrorMessage( i_res ) );
> goto browseActionCleanup;
> }
>
> i_res = UpnpAddToAction( &p_action, "Browse",
> - psz_service_type, "StartingIndex", psz_starting_index_ );
> + CONTENT_DIRECTORY_SERVICE_TYPE, "Filter", psz_filter_ );
>
> if ( i_res != UPNP_E_SUCCESS )
> {
> - msg_Dbg( _p_sd, "AddToAction 'StartingIndex' failed: %s",
> + msg_Dbg( _access, "AddToAction 'Filter' failed: %s",
> UpnpGetErrorMessage( i_res ) );
> goto browseActionCleanup;
> }
>
> i_res = UpnpAddToAction( &p_action, "Browse",
> - psz_service_type, "RequestedCount", psz_requested_count_ );
> + CONTENT_DIRECTORY_SERVICE_TYPE, "RequestedCount", psz_requested_count_ );
>
> if ( i_res != UPNP_E_SUCCESS )
> {
> - msg_Dbg( _p_sd, "AddToAction 'RequestedCount' failed: %s",
> + msg_Dbg( _access, "AddToAction 'RequestedCount' failed: %s",
> UpnpGetErrorMessage( i_res ) );
> goto browseActionCleanup;
> }
>
> i_res = UpnpAddToAction( &p_action, "Browse",
> - psz_service_type, "SortCriteria", psz_sort_criteria_ );
> + CONTENT_DIRECTORY_SERVICE_TYPE, "SortCriteria", psz_sort_criteria_ );
>
> if ( i_res != UPNP_E_SUCCESS )
> {
> - msg_Dbg( _p_sd, "AddToAction 'SortCriteria' failed: %s",
> + msg_Dbg( _access, "AddToAction 'SortCriteria' failed: %s",
> UpnpGetErrorMessage( i_res ) );
> goto browseActionCleanup;
> }
>
> - i_res = UpnpSendAction( _p_sd->p_sys->client_handle,
> + i_res = UpnpSendAction( _access->p_sys->p_upnp->handle(),
> psz_url,
> - psz_service_type,
> + CONTENT_DIRECTORY_SERVICE_TYPE,
> 0, /* ignored in SDK, must be NULL */
> p_action,
> &p_response );
>
> if ( i_res != UPNP_E_SUCCESS )
> {
> - msg_Err( _p_sd, "%s when trying the send() action with URL: %s",
> + msg_Err( _access, "%s when trying the send() action with URL: %s",
> UpnpGetErrorMessage( i_res ), psz_url );
>
> ixmlDocument_free( p_response );
> @@ -777,86 +687,51 @@ IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
> }
>
> browseActionCleanup:
> -
> - free( psz_service_type );
> -
> ixmlDocument_free( p_action );
> return p_response;
> }
>
> -void MediaServer::fetchContents()
> -{
> - /* Delete previous contents to prevent duplicate entries */
> - if ( _p_contents )
> - {
> - delete _p_contents;
> - services_discovery_RemoveItem( _p_sd, _p_input_item );
> - services_discovery_AddItem( _p_sd, _p_input_item, NULL );
> - }
> -
> - Container* root = new Container( 0, "0", getFriendlyName() );
> -
> - _fetchContents( root, 0 );
> -
> - _p_contents = root;
> - _p_contents->setInputItem( _p_input_item );
> -
> - _buildPlaylist( _p_contents, NULL );
> -}
> -
> /*
> * Fetches and parses the UPNP response
> */
> -bool MediaServer::_fetchContents( Container* p_parent, int i_offset )
> +bool MediaServer::fetchContents()
> {
> - if (!p_parent)
> - {
> - msg_Err( _p_sd, "No parent" );
> - return false;
> - }
> + const char* objectID = "";
> + vlc_url_t url;
> + vlc_UrlParse( &url, _access->psz_location, '?');
>
> - char* psz_starting_index;
> - if( asprintf( &psz_starting_index, "%d", i_offset ) < 0 )
> + if ( url.psz_option && !strncmp( url.psz_option, "ObjectID=", strlen( "ObjectID=" ) ) )
> {
> - msg_Err( _p_sd, "asprintf error:%d", i_offset );
> - return false;
> + objectID = &url.psz_option[strlen( "ObjectID=" )];
> }
>
> - IXML_Document* p_response = _browseAction( p_parent->getObjectID(),
> + IXML_Document* p_response = _browseAction( objectID,
> "BrowseDirectChildren",
> "id,dc:title,res," /* Filter */
> "sec:CaptionInfo,sec:CaptionInfoEx,"
> "pv:subtitlefile",
> - psz_starting_index, /* StartingIndex */
> "0", /* RequestedCount */
> "" /* SortCriteria */
> );
> - free( psz_starting_index );
> + vlc_UrlClean( &url );
> if ( !p_response )
> {
> - msg_Err( _p_sd, "No response from browse() action" );
> + msg_Err( _access, "No response from browse() action" );
> return false;
> }
>
> IXML_Document* p_result = parseBrowseResult( p_response );
> - int i_number_returned = xml_getNumber( p_response, "NumberReturned" );
> - int i_total_matches = xml_getNumber( p_response , "TotalMatches" );
> -
> -#ifndef NDEBUG
> - msg_Dbg( _p_sd, "i_offset[%d]i_number_returned[%d]_total_matches[%d]\n",
> - i_offset, i_number_returned, i_total_matches );
> -#endif
>
> ixmlDocument_free( p_response );
>
> if ( !p_result )
> {
> - msg_Err( _p_sd, "browse() response parsing failed" );
> + msg_Err( _access, "browse() response parsing failed" );
> return false;
> }
>
> #ifndef NDEBUG
> - msg_Dbg( _p_sd, "Got DIDL document: %s", ixmlPrintDocument( p_result ) );
> + msg_Dbg( _access, "Got DIDL document: %s", ixmlPrintDocument( p_result ) );
> #endif
>
> IXML_NodeList* containerNodeList =
> @@ -864,11 +739,9 @@ bool MediaServer::_fetchContents( Container* p_parent, int i_offset )
>
> if ( containerNodeList )
> {
> - for ( unsigned int i = 0;
> - i < ixmlNodeList_length( containerNodeList ); i++ )
> + for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ )
> {
> - IXML_Element* containerElement =
> - ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
> + IXML_Element* containerElement = (IXML_Element*)ixmlNodeList_item( containerNodeList, i );
>
> const char* objectID = ixmlElement_getAttribute( containerElement,
> "id" );
> @@ -877,13 +750,9 @@ bool MediaServer::_fetchContents( Container* p_parent, int i_offset )
>
> const char* title = xml_getChildElementValue( containerElement,
> "dc:title" );
> -
> if ( !title )
> continue;
> -
> - Container* container = new Container( p_parent, objectID, title );
> - p_parent->addContainer( container );
> - _fetchContents( container, 0 );
> + addItem(objectID, title);
> }
> ixmlNodeList_free( containerNodeList );
> }
> @@ -944,443 +813,187 @@ bool MediaServer::_fetchContents( Container* p_parent, int i_offset )
> i_seconds );
> }
>
> - Item* item = new Item( p_parent, objectID, title, psz_resource_url, psz_subtitles, i_duration );
> - p_parent->addItem( item );
> + addItem( title, objectID, psz_subtitles, i_duration, psz_resource_url );
> }
> ixmlNodeList_free( p_resource_list );
> }
> - else continue;
> + else
> + continue;
> }
> ixmlNodeList_free( itemNodeList );
> }
>
> ixmlDocument_free( p_result );
> -
> - if( i_offset + i_number_returned < i_total_matches )
> - return _fetchContents( p_parent, i_offset + i_number_returned );
> -
> return true;
> }
>
> -// TODO: Create a permanent fix for the item duplication bug. The current fix
> -// is essentially only a small hack. Although it fixes the problem, it introduces
> -// annoying cosmetic issues with the playlist. For example, when the UPnP Server
> -// rebroadcasts it's directory structure, the VLC Client deletes the old directory
> -// structure, causing the user to go back to the root node of the directory. The
> -// directory is then rebuilt, and the user is forced to traverse through the directory
> -// to find the item they were looking for. Some servers may not push the directory
> -// structure too often, but we cannot rely on this fix.
> -//
> -// I have thought up another fix, but this would require certain features to
> -// be present within the VLC services discovery. Currently, services_discovery_AddItem
> -// does not allow the programmer to nest items. It only allows a "2 deep" scope.
> -// An example of the limitation is below:
> -//
> -// Root Directory
> -// + Item 1
> -// + Item 2
> -//
> -// services_discovery_AddItem will not let the programmer specify a child-node to
> -// insert items into, so we would not be able to do the following:
> -//
> -// Root Directory
> -// + Item 1
> -// + Sub Item 1
> -// + Item 2
> -// + Sub Item 1 of Item 2
> -// + Sub-Sub Item 1 of Sub Item 1
> -//
> -// This creates a HUGE limitation on what we are able to do. If we were able to do
> -// the above, we could simply preserve the old directory listing, and compare what items
> -// do not exist in the new directory listing, then remove them from the shown listing using
> -// services_discovery_RemoveItem. If new files were introduced within an already existing
> -// container, we could simply do so with services_discovery_AddItem.
> -
> -/*
> - * Builds playlist based on available input items.
> - */
> -void MediaServer::_buildPlaylist( Container* p_parent, input_item_node_t *p_input_node )
> +static int ReadDirectory( access_t *p_access, input_item_node_t* p_node )
> {
> - bool b_send = p_input_node == NULL;
> - if( b_send )
> - p_input_node = input_item_node_Create( p_parent->getInputItem() );
> -
> - for ( unsigned int i = 0; i < p_parent->getNumContainers(); i++ )
> - {
> - Container* p_container = p_parent->getContainer( i );
> -
> - input_item_t* p_input_item = input_item_New( "vlc://nop",
> - p_container->getTitle() );
> - input_item_node_t *p_new_node =
> - input_item_node_AppendItem( p_input_node, p_input_item );
> + MediaServer server( p_access->psz_location, p_access, p_node );
>
> - p_container->setInputItem( p_input_item );
> - _buildPlaylist( p_container, p_new_node );
> - }
> + if ( !server.fetchContents() )
> + return VLC_EGENERIC;
> + return VLC_SUCCESS;
> +}
>
> - for ( unsigned int i = 0; i < p_parent->getNumItems(); i++ )
> +static int Control( access_t *, int i_query, va_list args )
> +{
> + switch ( i_query )
> {
> - Item* p_item = p_parent->getItem( i );
> -
> - char **ppsz_opts = NULL;
> - char *psz_input_slave = p_item->buildInputSlaveOption();
> - if( psz_input_slave )
> - {
> - ppsz_opts = (char**)malloc( 2 * sizeof( char* ) );
> - ppsz_opts[0] = psz_input_slave;
> - ppsz_opts[1] = p_item->buildSubTrackIdOption();
> - }
> -
> - input_item_t* p_input_item = input_item_NewExt( p_item->getResource(),
> - p_item->getTitle(),
> - psz_input_slave ? 2 : 0,
> - psz_input_slave ? ppsz_opts : NULL,
> - VLC_INPUT_OPTION_TRUSTED, /* XXX */
> - p_item->getDuration() );
> + case ACCESS_CAN_SEEK:
> + case ACCESS_CAN_FASTSEEK:
> + case ACCESS_CAN_PAUSE:
> + case ACCESS_CAN_CONTROL_PACE:
> + *va_arg( args, bool* ) = false;
> + break;
>
> - assert( p_input_item );
> - if( ppsz_opts )
> - {
> - free( ppsz_opts[0] );
> - free( ppsz_opts[1] );
> - free( ppsz_opts );
> + case ACCESS_GET_SIZE:
> + {
> + *va_arg( args, uint64_t * ) = 0;
> + break;
> + }
> + case ACCESS_GET_PTS_DELAY:
> + *va_arg( args, int64_t * ) = 0;
> + break;
>
> - psz_input_slave = NULL;
> - }
> + case ACCESS_SET_PAUSE_STATE:
> + /* Nothing to do */
> + break;
>
> - input_item_node_AppendItem( p_input_node, p_input_item );
> - p_item->setInputItem( p_input_item );
> + default:
> + return VLC_EGENERIC;
> }
> -
> - if( b_send )
> - input_item_node_PostAndDelete( p_input_node );
> + return VLC_SUCCESS;
> }
>
> -void MediaServer::setInputItem( input_item_t* p_input_item )
> +static int Open( vlc_object_t *p_this )
> {
> - if( _p_input_item == p_input_item )
> - return;
> + access_t* p_access = (access_t*)p_this;
> + access_sys_t* p_sys = new(std::nothrow) access_sys_t;
> + if ( unlikely( !p_sys ) )
> + return VLC_ENOMEM;
>
> - if( _p_input_item )
> - vlc_gc_decref( _p_input_item );
> + p_access->p_sys = p_sys;
> + p_sys->p_upnp = UpnpInstanceWrapper::get( p_this, NULL, NULL );
> + if ( !p_sys->p_upnp )
> + {
> + delete p_sys;
> + return VLC_EGENERIC;
> + }
>
> - vlc_gc_incref( p_input_item );
> - _p_input_item = p_input_item;
> -}
> + p_access->pf_readdir = ReadDirectory;
> + ACCESS_SET_CALLBACKS( NULL, NULL, Control, NULL );
>
> -input_item_t* MediaServer::getInputItem() const
> -{
> - return _p_input_item;
> + return VLC_SUCCESS;
> }
>
> -bool MediaServer::compareSID( const char* psz_sid )
> +static void Close( vlc_object_t* p_this )
> {
> - return ( strncmp( _subscription_id, psz_sid, sizeof( Upnp_SID ) ) == 0 );
> + access_t* p_access = (access_t*)p_this;
> + p_access->p_sys->p_upnp->release( false );
> + delete p_access->p_sys;
> }
>
> -
> -/*
> - * MediaServerList class
> - */
> -MediaServerList::MediaServerList( services_discovery_t* p_sd )
> -{
> - _p_sd = p_sd;
> }
>
> -MediaServerList::~MediaServerList()
> +UpnpInstanceWrapper::UpnpInstanceWrapper()
> + : _opaque( NULL )
> + , _callback( NULL )
> + , _refcount( 0 )
> {
> - for ( unsigned int i = 0; i < _list.size(); i++ )
> - {
> - delete _list[i];
> - }
> }
>
> -bool MediaServerList::addServer( MediaServer* p_server )
> +UpnpInstanceWrapper::~UpnpInstanceWrapper()
> {
> - input_item_t* p_input_item = NULL;
> - if ( getServer( p_server->getUDN() ) != 0 ) return false;
> -
> - msg_Dbg( _p_sd, "Adding server '%s' with uuid '%s'", p_server->getFriendlyName(), p_server->getUDN() );
> -
> - p_input_item = input_item_New( "vlc://nop", p_server->getFriendlyName() );
> -
> - input_item_SetDescription( p_input_item, p_server->getUDN() );
> -
> - p_server->setInputItem( p_input_item );
> -
> - services_discovery_AddItem( _p_sd, p_input_item, NULL );
> -
> - _list.push_back( p_server );
> -
> - return true;
> + UpnpUnRegisterClient( _handle );
> + UpnpFinish();
> }
>
> -MediaServer* MediaServerList::getServer( const char* psz_udn )
> +UpnpInstanceWrapper *UpnpInstanceWrapper::get(vlc_object_t *p_obj, Upnp_FunPtr callback, SD::MediaServerList *opaque)
> {
> - MediaServer* p_result = 0;
> -
> - for ( unsigned int i = 0; i < _list.size(); i++ )
> + vlc_mutex_locker lock( &s_lock );
> + if ( s_instance == NULL )
> {
> - if( strcmp( psz_udn, _list[i]->getUDN() ) == 0 )
> + UpnpInstanceWrapper* instance = new(std::nothrow) UpnpInstanceWrapper;
> + if ( unlikely( !instance ) )
> + return NULL;
> +
> + #ifdef UPNP_ENABLE_IPV6
> + char* psz_miface = var_InheritString( p_obj, "miface" );
> + msg_Info( p_obj, "Initializing libupnp on '%s' interface", psz_miface );
> + int i_res = UpnpInit2( psz_miface, 0 );
> + free( psz_miface );
> + #else
> + /* If UpnpInit2 isnt available, initialize on first IPv4-capable interface */
> + int i_res = UpnpInit( 0, 0 );
> + #endif
> + if( i_res != UPNP_E_SUCCESS )
> {
> - p_result = _list[i];
> - break;
> + msg_Err( p_obj, "Initialization failed: %s", UpnpGetErrorMessage( i_res ) );
> + delete instance;
> + return NULL;
> }
> - }
>
> - return p_result;
> -}
> -
> -MediaServer* MediaServerList::getServerBySID( const char* psz_sid )
> -{
> - MediaServer* p_server = 0;
> + ixmlRelaxParser( 1 );
>
> - for ( unsigned int i = 0; i < _list.size(); i++ )
> - {
> - if ( _list[i]->compareSID( psz_sid ) )
> + /* Register a control point */
> + i_res = UpnpRegisterClient( Callback, instance, &instance->_handle );
> + if( i_res != UPNP_E_SUCCESS )
> {
> - p_server = _list[i];
> - break;
> + msg_Err( p_obj, "Client registration failed: %s", UpnpGetErrorMessage( i_res ) );
> + delete instance;
> + return NULL;
> }
> - }
> -
> - return p_server;
> -}
> -
> -void MediaServerList::removeServer( const char* psz_udn )
> -{
> - MediaServer* p_server = getServer( psz_udn );
> - if ( !p_server ) return;
> -
> - msg_Dbg( _p_sd, "Removing server '%s'", p_server->getFriendlyName() );
>
> - services_discovery_RemoveItem( _p_sd, p_server->getInputItem() );
> -
> - std::vector<MediaServer*>::iterator it;
> - for ( it = _list.begin(); it != _list.end(); ++it )
> - {
> - if ( *it == p_server )
> + /* libupnp does not treat a maximum content length of 0 as unlimited
> + * until 64dedf (~ pupnp v1.6.7) and provides no sane way to discriminate
> + * between versions */
> + if( (i_res = UpnpSetMaxContentLength( INT_MAX )) != UPNP_E_SUCCESS )
> {
> - _list.erase( it );
> - delete p_server;
> - break;
> + msg_Err( p_obj, "Failed to set maximum content length: %s",
> + UpnpGetErrorMessage( i_res ));
> + delete instance;
> + return NULL;
> }
> + s_instance = instance;
> }
> -}
> -
> -
> -/*
> - * Item class
> - */
> -Item::Item( Container* p_parent,
> - const char* psz_object_id, const char* psz_title,
> - const char* psz_resource, const char* psz_subtitles,
> - mtime_t i_duration )
> -{
> - _parent = p_parent;
> -
> - _objectID = psz_object_id;
> - _title = psz_title;
> - _resource = psz_resource;
> - _subtitles = psz_subtitles ? psz_subtitles : "";
> - _duration = i_duration;
> -
> - _p_input_item = NULL;
> -}
> -
> -Item::~Item()
> -{
> - if( _p_input_item )
> - vlc_gc_decref( _p_input_item );
> -}
> -
> -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();
> -}
> -
> -const char* Item::getSubtitles() const
> -{
> - if( !_subtitles.size() )
> - return NULL;
> -
> - return _subtitles.c_str();
> -}
> -
> -mtime_t Item::getDuration() const
> -{
> - return _duration;
> -}
> -
> -char* Item::buildInputSlaveOption() const
> -{
> - const char *psz_subtitles = getSubtitles();
> -
> - const char *psz_scheme_delim = "://";
> - const char *psz_sub_opt_fmt = ":input-slave=%s/%s://%s";
> - const char *psz_demux = "subtitle";
> -
> - char *psz_uri_scheme = NULL;
> - const char *psz_scheme_end = NULL;
> - const char *psz_uri_location = NULL;
> - char *psz_input_slave = NULL;
> -
> - size_t i_scheme_len;
> -
> - if( !psz_subtitles )
> - return NULL;
> -
> - psz_scheme_end = strstr( psz_subtitles, psz_scheme_delim );
> -
> - /* subtitles not being an URI would make no sense */
> - if( !psz_scheme_end )
> - return NULL;
> -
> - i_scheme_len = psz_scheme_end - psz_subtitles;
> - psz_uri_scheme = (char*)malloc( i_scheme_len + 1 );
> -
> - if( !psz_uri_scheme )
> - return NULL;
> -
> - memcpy( psz_uri_scheme, psz_subtitles, i_scheme_len );
> - psz_uri_scheme[i_scheme_len] = '\0';
> -
> - /* If the subtitles try to force a vlc demux,
> - * then something is very wrong */
> - if( strchr( psz_uri_scheme, '/' ) )
> + s_instance->_refcount++;
> + // This assumes a single UPNP SD instance
> + if (callback && opaque)
> {
> - free( psz_uri_scheme );
> - return NULL;
> + assert(!s_instance->_callback && !s_instance->_opaque);
> + s_instance->_opaque = opaque;
> + s_instance->_callback = callback;
> }
> -
> - psz_uri_location = psz_scheme_end + strlen( psz_scheme_delim );
> -
> - if( -1 == asprintf( &psz_input_slave, psz_sub_opt_fmt,
> - psz_uri_scheme, psz_demux, psz_uri_location ) )
> - psz_input_slave = NULL;
> -
> - free( psz_uri_scheme );
> - return psz_input_slave;
> + return s_instance;
> }
>
> -char* Item::buildSubTrackIdOption() const
> +void UpnpInstanceWrapper::release(bool isSd)
> {
> - return strdup( ":sub-track-id=2" );
> -}
> -
> -void Item::setInputItem( input_item_t* p_input_item )
> -{
> - if( _p_input_item == p_input_item )
> - return;
> -
> - if( _p_input_item )
> - vlc_gc_decref( _p_input_item );
> -
> - vlc_gc_incref( p_input_item );
> - _p_input_item = p_input_item;
> -}
> -
> -/*
> - * Container class
> - */
> -Container::Container( Container* p_parent,
> - const char* psz_object_id,
> - const char* psz_title )
> -{
> - _parent = p_parent;
> -
> - _objectID = psz_object_id;
> - _title = psz_title;
> -
> - _p_input_item = NULL;
> -}
> -
> -Container::~Container()
> -{
> - for ( unsigned int i = 0; i < _containers.size(); i++ )
> + vlc_mutex_locker lock( &s_lock );
> + if ( isSd )
> {
> - delete _containers[i];
> + _callback = NULL;
> + _opaque = NULL;
> }
> -
> - for ( unsigned int i = 0; i < _items.size(); i++ )
> + if (--s_instance->_refcount == 0)
> {
> - delete _items[i];
> + delete s_instance;
> + s_instance = NULL;
> }
> -
> - if( _p_input_item )
> - vlc_gc_decref( _p_input_item );
> -}
> -
> -void Container::addItem( Item* item )
> -{
> - _items.push_back( item );
> -}
> -
> -void Container::addContainer( Container* p_container )
> -{
> - _containers.push_back( p_container );
> -}
> -
> -const char* Container::getObjectID() const
> -{
> - return _objectID.c_str();
> }
>
> -const char* Container::getTitle() const
> +UpnpClient_Handle UpnpInstanceWrapper::handle() const
> {
> - return _title.c_str();
> + return _handle;
> }
>
> -unsigned int Container::getNumItems() const
> +int UpnpInstanceWrapper::Callback(Upnp_EventType event_type, void *p_event, void *p_user_data)
> {
> - return _items.size();
> -}
> -
> -unsigned int Container::getNumContainers() const
> -{
> - return _containers.size();
> -}
> -
> -Item* Container::getItem( unsigned int i_index ) const
> -{
> - if ( i_index < _items.size() ) return _items[i_index];
> - return 0;
> -}
> -
> -Container* Container::getContainer( unsigned int i_index ) const
> -{
> - if ( i_index < _containers.size() ) return _containers[i_index];
> + UpnpInstanceWrapper* self = static_cast<UpnpInstanceWrapper*>( p_user_data );
> + vlc_mutex_locker lock( &self->s_lock );
> + if ( !self->_callback )
> + return 0;
> + self->_callback( event_type, p_event, self->_opaque );
> return 0;
> }
> -
> -Container* Container::getParent()
> -{
> - return _parent;
> -}
> -
> -void Container::setInputItem( input_item_t* p_input_item )
> -{
> - if( _p_input_item == p_input_item )
> - return;
> -
> - if( _p_input_item )
> - vlc_gc_decref( _p_input_item );
> -
> - vlc_gc_incref( p_input_item );
> - _p_input_item = p_input_item;
> -}
> -
> -input_item_t* Container::getInputItem() const
> -{
> - return _p_input_item;
> -}
> diff --git a/modules/services_discovery/upnp.hpp b/modules/services_discovery/upnp.hpp
> index 23fe4db..971b709 100644
> --- a/modules/services_discovery/upnp.hpp
> +++ b/modules/services_discovery/upnp.hpp
> @@ -7,6 +7,7 @@
> * Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin)
> * Christian Henz <henz # c-lab.de>
> * Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
> + * Hugo Beauzée-Luyssen <hugo at beauzee.fr>
> *
> * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
> *
> @@ -33,62 +34,55 @@
>
> #include <vlc_common.h>
>
> -// Classes
> -class Container;
> -
> -class MediaServer
> +namespace SD
> {
> -public:
> -
> - static void parseDeviceDescription( IXML_Document* p_doc,
> - const char* psz_location,
> - services_discovery_t* p_sd );
> -
> - MediaServer( const char* psz_udn,
> - const char* psz_friendly_name,
> - services_discovery_t* p_sd );
> -
> - ~MediaServer();
> -
> - const char* getUDN() const;
> - const char* getFriendlyName() const;
> -
> - void setContentDirectoryEventURL( const char* psz_url );
> - const char* getContentDirectoryEventURL() const;
> -
> - void setContentDirectoryControlURL( const char* psz_url );
> - const char* getContentDirectoryControlURL() const;
> -
> - void subscribeToContentDirectory();
> - void fetchContents();
> -
> - void setInputItem( input_item_t* p_input_item );
> - input_item_t* getInputItem() const;
> -
> - bool compareSID( const char* psz_sid );
> + class MediaServerList;
> +}
> +
> +/*
> + * libUpnp allows only one instance per process, so we have to share one for
> + * both SD & Access module
> + * Since the callback is bound to the UpnpClient_Handle, we have to register
> + * a wrapper callback, in order for the access module to be able to initialize
> + * libUpnp first.
> + * When a SD wishes to use libUpnp, it will provide its own callback, that the
> + * wrapper will forward.
> + * This way, we always have a register callback & a client handle.
> + */
> +class UpnpInstanceWrapper
> +{
> +public:
> + // This increases the refcount before returning the instance
> + static UpnpInstanceWrapper* get(vlc_object_t* p_obj, Upnp_FunPtr callback, SD::MediaServerList *opaque);
> + void release(bool isSd);
> + UpnpClient_Handle handle() const;
>
> private:
> + static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data );
>
> - bool _fetchContents( Container* p_parent, int i_starting_index );
> - void _buildPlaylist( Container* p_container, input_item_node_t *p_item_node );
> -
> - IXML_Document* _browseAction( const char*, const char*,
> - const char*, const char*, const char*, const char* );
> -
> - services_discovery_t* _p_sd;
> -
> - Container* _p_contents;
> - input_item_t* _p_input_item;
> + UpnpInstanceWrapper();
> + ~UpnpInstanceWrapper();
>
> - std::string _UDN;
> - std::string _friendly_name;
> +private:
> + static UpnpInstanceWrapper* s_instance;
> + static vlc_mutex_t s_lock;
> + UpnpClient_Handle _handle;
> + SD::MediaServerList* _opaque;
> + Upnp_FunPtr _callback;
> + int _refcount;
Identifiers with leading underscores are reserved for the implementation.
> +};
>
> - std::string _content_directory_event_url;
> - std::string _content_directory_control_url;
> +namespace SD
> +{
>
> - int _i_subscription_timeout;
> - int _i_content_directory_service_version;
> - Upnp_SID _subscription_id;
> +struct MediaServerDesc
> +{
> + MediaServerDesc(const std::string& udn, const std::string& fName, const std::string& loc);
> + ~MediaServerDesc();
> + std::string UDN;
> + std::string friendlyName;
> + std::string location;
> + input_item_t* inputItem;
> };
>
>
> @@ -99,87 +93,44 @@ public:
> MediaServerList( services_discovery_t* p_sd );
> ~MediaServerList();
>
> - bool addServer( MediaServer* p_server );
> - void removeServer( const char* psz_udn );
> -
> - MediaServer* getServer( const char* psz_udn );
> - MediaServer* getServerBySID( const char* psz_sid );
> + bool addServer(MediaServerDesc *desc );
> + void removeServer(const std::string &udn );
> + MediaServerDesc* getServer( const std::string& udn );
> + static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data );
>
> private:
> + void parseNewServer( IXML_Document* doc, const std::string& location );
>
> +private:
> services_discovery_t* _p_sd;
> -
> - std::vector<MediaServer*> _list;
> + std::vector<MediaServerDesc*> _list;
> + vlc_mutex_t _lock;
same
> };
>
> +}
>
> -class Item
> +namespace Access
> {
> -public:
> -
> - Item( Container* parent,
> - const char* objectID,
> - const char* title,
> - const char* subtitles,
> - const char* resource,
> - mtime_t duration );
> - ~Item();
> -
> - const char* getObjectID() const;
> - const char* getTitle() const;
> - const char* getResource() const;
> - const char* getSubtitles() const;
> - char* buildInputSlaveOption() const;
> - char* buildSubTrackIdOption() const;
> - mtime_t getDuration() const;
> -
> - void setInputItem( input_item_t* p_input_item );
> -
> -private:
> -
> - input_item_t* _p_input_item;
>
> - Container* _parent;
> - std::string _objectID;
> - std::string _title;
> - std::string _resource;
> - std::string _subtitles;
> - mtime_t _duration;
> -};
> -
> -
> -class Container
> +class MediaServer
> {
> 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;
> - Container* getParent();
> -
> - void setInputItem( input_item_t* p_input_item );
> - input_item_t* getInputItem() const;
> + MediaServer( const char* psz_url, access_t* p_access, input_item_node_t* node );
> + bool fetchContents();
>
> private:
> + MediaServer(const MediaServer&);
> + MediaServer& operator=(const MediaServer&);
>
> - input_item_t* _p_input_item;
> + void addItem(const char* objectID, const char* title);
> + void addItem(const char* title, const char* psz_objectID, const char* psz_subtitles, mtime_t duration, const char* psz_url );
>
> - Container* _parent;
> + IXML_Document* _browseAction(const char*, const char*,
> + const char*, const char*, const char* );
>
> - std::string _objectID;
> - std::string _title;
> - std::vector<Item*> _items;
> - std::vector<Container*> _containers;
> + const std::string _url;
> + access_t* _access;
> + input_item_node_t* _node;
same
> };
>
> +}
> --
> 2.1.0
>
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel
More information about the vlc-devel
mailing list