[vlc-devel] [PATCH] [WIP] Partial UPnP rewrite

Hugo Beauzée-Luyssen hugo at beauzee.fr
Tue Mar 10 18:59:05 CET 2015


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 );
+        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 ) +
                             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 );
+
+                            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;
+};
 
-    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;
 };
 
+}
 
-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;
 };
 
+}
-- 
2.1.0




More information about the vlc-devel mailing list