133 changes: 133 additions & 0 deletions mythtv/libs/libmythupnp/mmulticastsocketdevice.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//////////////////////////////////////////////////////////////////////////////
// Program Name: mmulticastsocketdevice.cpp
// Created : Oct. 1, 2005
//
// Purpose : Multicast QSocketDevice Implmenetation
//
// Copyright (c) 2005 David Blain <mythtv@theblains.net>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or at your option any later version of the LGPL.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library. If not, see <http://www.gnu.org/licenses/>.
//
//////////////////////////////////////////////////////////////////////////////

#include <errno.h>

#ifdef _WIN32
# include <winsock2.h>
# include <ws2tcpip.h>
# define GET_SOCKET_ERROR WSAGetLastError()
#else
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# define GET_SOCKET_ERROR errno
#endif

// Qt headers
#include <QStringList>

// MythTV headers
#include "mmulticastsocketdevice.h"
#include "mythverbose.h"

#define LOC (QString("MMulticastSocketDevice(%1:%2): ") \
.arg(m_address.toString()).arg(socket()))
#define LOC_WARN (QString("MMulticastSocketDevice(%1:%2) Warning: ") \
.arg(m_address.toString()).arg(socket()))
#define LOC_ERR (QString("MMulticastSocketDevice(%1:%2) Error: ") \
.arg(m_address.toString()).arg(socket()))

MMulticastSocketDevice::MMulticastSocketDevice(
QString sAddress, quint16 nPort, u_char ttl) :
MSocketDevice(MSocketDevice::Datagram),
m_address(sAddress), m_port(nPort)
{
// ttl = UPnp::GetConfiguration()->GetValue( "UPnP/TTL", 4 );

if (ttl == 0)
ttl = 4;

setProtocol(IPv4);
setSocket(createNewSocket(), MSocketDevice::Datagram);

m_imr.imr_multiaddr.s_addr = inet_addr(sAddress.toAscii().constData());
m_imr.imr_interface.s_addr = htonl(INADDR_ANY);

if (setsockopt(socket(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
&m_imr, sizeof( m_imr )) < 0)
{
VERBOSE(VB_IMPORTANT, LOC_ERR +
"setsockopt - IP_ADD_MEMBERSHIP " + ENO);
}

if (setsockopt(socket(), IPPROTO_IP, IP_MULTICAST_TTL,
&ttl, sizeof(ttl)) < 0)
{
VERBOSE(VB_IMPORTANT, LOC_ERR +
"setsockopt - IP_MULTICAST_TTL " + ENO);
}

setAddressReusable(true);

if (bind(m_address, m_port) < 0)
VERBOSE(VB_IMPORTANT, LOC_ERR + "bind failed " + ENO);
}

MMulticastSocketDevice::~MMulticastSocketDevice()
{
if (!m_address.isNull() &&
(setsockopt(socket(), IPPROTO_IP, IP_DROP_MEMBERSHIP,
(char*)(&m_imr), sizeof(m_imr) < 0)))
{
// This isn't really an error, we will drop out of
// the group anyway when we close the socket.
VERBOSE(VB_IMPORTANT|VB_EXTRA, LOC +
"setsockopt - IP_DROP_MEMBERSHIP " + ENO);
}
}

qint64 MMulticastSocketDevice::writeBlock(
const char *data, quint64 len,
const QHostAddress & host, quint16 port)
{
#ifdef IP_MULTICAST_IF
if (host.toString() == "239.255.255.250")
{
QList<QHostAddress>::const_iterator it = m_local_addresses.begin();
int retx = 0;
for (; it != m_local_addresses.end(); ++it)
{
if ((*it).protocol() != QAbstractSocket::IPv4Protocol)
continue; // skip IPv6 addresses

QString addr = (*it).toString();
if (addr == "127.0.0.1")
continue; // skip localhost address

struct in_addr interface_addr;
inet_pton(AF_INET, addr.toAscii().constData(),
(char*)&interface_addr);
setsockopt(socket(), IPPROTO_IP, IP_MULTICAST_IF,
&interface_addr, sizeof(interface_addr));
retx = MSocketDevice::writeBlock(data, len, host, port);
//VERBOSE(VB_IMPORTANT, QString("writeBlock on %1 %2")
// .arg((*it).toString()).arg((retx==(int)len)?"ok":"err"));
usleep(5000 + (rand() % 5000));
}
return retx;
}
#endif

return MSocketDevice::writeBlock(data, len, host, port);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//////////////////////////////////////////////////////////////////////////////
// Program Name: multicast.h
// Program Name: mmulticastsocketdevice.h
// Created : Oct. 1, 2005
//
// Purpose : Multicast QSocketDevice Implmenetation
Expand All @@ -21,38 +21,58 @@
//
//////////////////////////////////////////////////////////////////////////////

#ifndef __MULTICAST_H__
#define __MULTICAST_H__
#ifndef _MULTICAST_SOCKET_DEVICE_H_
#define _MULTICAST_SOCKET_DEVICE_H_

#include <sys/socket.h>
#include <netinet/ip.h>

// Qt headers
#include <QString>
#include <QNetworkInterface>
#include <QHostAddress>
#include <QByteArray>
#include <QString>

// MythTV headers
#include "msocketdevice.h"
#include "mythverbose.h"
#include "compat.h"

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
//
// QMulticastSocket Class Definition/Implementation
// MMulticastSocketDevice Class Definition/Implementation
//
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////

// -=>TODO: Need to add support for Multi-Homed machines.

class QMulticastSocket : public MSocketDevice
class MMulticastSocketDevice : public MSocketDevice
{
public:
public:
MMulticastSocketDevice() :
MSocketDevice(MSocketDevice::Datagram),
m_local_addresses(QNetworkInterface::allAddresses()),
m_port(0)
{
memset(&m_imr, 0, sizeof(struct ip_mreq));
}

MMulticastSocketDevice(QString sAddress, quint16 nPort, u_char ttl = 0);

virtual ~MMulticastSocketDevice();

QHostAddress m_address;
quint16 m_port;

public:
virtual qint64 writeBlock(
const char *data, quint64 len,
const QHostAddress & host, quint16 port);

QMulticastSocket( QString sAddress, quint16 nPort, quint8 ttl = 0 );
virtual QHostAddress address() const { return m_address; }
virtual quint16 port() const { return m_port; }

virtual ~QMulticastSocket();
private:
QList<QHostAddress> m_local_addresses;
QHostAddress m_address;
quint16 m_port;
struct ip_mreq m_imr;
};

#endif
#endif // _MULTICAST_SOCKET_DEVICE_H_
108 changes: 0 additions & 108 deletions mythtv/libs/libmythupnp/multicast.cpp

This file was deleted.

4 changes: 3 additions & 1 deletion mythtv/libs/libmythupnp/mythxmlclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
//////////////////////////////////////////////////////////////////////////////

#include "mythxmlclient.h"
#include "mythverbose.h"

#include <QObject>
#include "mythverbose.h"
Expand Down Expand Up @@ -63,7 +64,8 @@ UPnPResultCode MythXMLClient::GetConnectionInfo( const QString &sPin, DatabasePa

list.insert( "Pin", sPin );

QDomDocument xmlResults = SendSOAPRequest( "GetConnectionInfo", list, nErrCode, sErrDesc, m_bInQtThread );
QDomDocument xmlResults = SendSOAPRequest(
"GetConnectionInfo", list, nErrCode, sErrDesc, m_bInQtThread);

// --------------------------------------------------------------
// Is this a valid response?
Expand Down
248 changes: 152 additions & 96 deletions mythtv/libs/libmythupnp/soapclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,113 +28,139 @@
#include "mythverbose.h"
#include "httprequest.h"

/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////

SOAPClient::SOAPClient( const QUrl &url,
const QString &sNamespace,
const QString &sControlPath )
#define LOC QString("SOAPClient: ")
#define LOC_WARN QString("SOAPClient, Warning: ")
#define LOC_ERR QString("SOAPClient, Error: ")


/** \brief Full SOAPClient constructor. After constructing the client
* with this constructor it is ready for SendSOAPRequest().
* \param url The host and port portion of the command URL
* \param sNamespace The part of the action before the # character
* \param sControlPath The path portion of the command URL
*/
SOAPClient::SOAPClient(const QUrl &url,
const QString &sNamespace,
const QString &sControlPath) :
m_url(url), m_sNamespace(sNamespace), m_sControlPath(sControlPath)
{
m_url = url;
m_sNamespace = sNamespace;
m_sControlPath = sControlPath;
}

/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////

SOAPClient::~SOAPClient()
/** \brief SOAPClient Initializer. After constructing the client
* with the empty constructor call this before calling
* SendSOAPRequest() the first time.
*/
bool SOAPClient::Init(const QUrl &url,
const QString &sNamespace,
const QString &sControlPath)
{
}

//////////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////
bool ok = true;
if (sNamespace.isEmpty())
{
ok = false;
VERBOSE(VB_IMPORTANT, LOC_ERR + "Init() given blank namespace");
}

QDomNode SOAPClient::FindNode( const QString &sName, QDomNode &baseNode )
{
QStringList parts = sName.split('/', QString::SkipEmptyParts);
QUrl test(url);
test.setPath(sControlPath);
if (!test.isValid())
{
ok = false;
VERBOSE(VB_IMPORTANT, LOC_ERR +
QString("Init() given invalid control URL %1")
.arg(test.toString()));
}

return FindNode( parts, baseNode );
if (ok)
{
m_url = url;
m_sNamespace = sNamespace;
m_sControlPath = sControlPath;
}
else
{
m_url = QUrl();
m_sNamespace.clear();
m_sControlPath.clear();
}

return ok;
}

//////////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////
/// Used by GeNodeValue() methods to find the named node.
QDomNode SOAPClient::FindNode(
const QString &sName, const QDomNode &baseNode) const
{
QStringList parts = sName.split('/', QString::SkipEmptyParts);
return FindNodeInternal(parts, baseNode);
}

QDomNode SOAPClient::FindNode( QStringList &sParts, QDomNode &curNode )
/// This is an internal function used to implement FindNode
QDomNode SOAPClient::FindNodeInternal(
QStringList &sParts, const QDomNode &curNode) const
{
if (sParts.empty())
return curNode;

QString sName = sParts.front();
sParts.pop_front();

QDomNode child = curNode.namedItem( sName );
QDomNode child = curNode.namedItem(sName);

if (child.isNull() )
sParts.clear();

return FindNode( sParts, child );
return FindNodeInternal(sParts, child);
}

/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////

int SOAPClient::GetNodeValue( QDomNode &node, const QString &sName, int nDefault )
/// Gets the named value using QDomNode as the baseNode in the search,
/// returns default if it is not found.
int SOAPClient::GetNodeValue(
const QDomNode &node, const QString &sName, int nDefault) const
{
QString sValue = GetNodeValue( node, sName, QString::number( nDefault ) );

QString sValue = GetNodeValue(node, sName, QString::number(nDefault));
return sValue.toInt();
}

/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////

bool SOAPClient::GetNodeValue( QDomNode &node, const QString &sName, bool bDefault )
/// Gets the named value using QDomNode as the baseNode in the search,
/// returns default if it is not found.
bool SOAPClient::GetNodeValue(
const QDomNode &node, const QString &sName, bool bDefault) const
{
QString sDefault = (bDefault) ? "true" : "false";
QString sValue = GetNodeValue( node, sName, sDefault );
QString sValue = GetNodeValue(node, sName, sDefault);
if (sValue.isEmpty())
return bDefault;

if (sValue.startsWith( 'T' , Qt::CaseInsensitive ) ||
sValue.startsWith( 'Y' , Qt::CaseInsensitive ) ||
sValue.startsWith( '1' , Qt::CaseInsensitive ) )
char ret = sValue[0].toAscii();
switch (ret)
{
return true;
case 't': case 'T': case 'y': case 'Y': case '1':
return true;
case 'f': case 'F': case 'n': case 'N': case '0':
return false;
default:
return bDefault;
}

if (sValue.startsWith( 'F' , Qt::CaseInsensitive ) ||
sValue.startsWith( 'N' , Qt::CaseInsensitive ) ||
sValue.startsWith( '0' , Qt::CaseInsensitive ) )
{
return false;
}

return bDefault;
}

/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////

QString SOAPClient::GetNodeValue( QDomNode &node, const QString &sName, const QString &sDefault )
/// Gets the named value using QDomNode as the baseNode in the search,
/// returns default if it is not found.
QString SOAPClient::GetNodeValue(
const QDomNode &node, const QString &sName, const QString &sDefault) const
{
if (node.isNull())
return sDefault;

QString sValue = "";
QDomNode valNode = FindNode( sName, node );
QDomNode valNode = FindNode(sName, node);

if (!valNode.isNull())
{
// -=>TODO: Assumes first child is Text Node.

QDomText oText = valNode.firstChild().toText();
QDomText oText = valNode.firstChild().toText();

if (!oText.isNull())
sValue = oText.nodeValue();
Expand All @@ -145,29 +171,40 @@ QString SOAPClient::GetNodeValue( QDomNode &node, const QString &sName, const QS
return sDefault;
}

/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////

QDomDocument SOAPClient::SendSOAPRequest( const QString &sMethod,
QStringMap &list,
int &nErrCode,
QString &sErrDesc,
bool bInQtThread )
/** Actually sends the sMethod action to the command URL specified
* in the constructor (url+[/]+sControlPath). list is parsed as
* a series of key value pairs for the input params and then
* cleared and used for the output params.
*
* \param bInQtThread May be set to true if this is run from within
* a QThread with a running an event loop.
*/
QDomDocument SOAPClient::SendSOAPRequest(const QString &sMethod,
QStringMap &list,
int &nErrCode,
QString &sErrDesc,
bool bInQtThread)
{
QUrl url( m_url );
QUrl url(m_url);

url.setPath(m_sControlPath);

url.setPath( m_sControlPath );
if (m_sNamespace.isEmpty())
{
nErrCode = 0;
sErrDesc = "No namespace given";
return QDomDocument();
}

// --------------------------------------------------------------
// Add appropriate headers
// --------------------------------------------------------------

QHttpRequestHeader header;
QHttpRequestHeader header("POST", sMethod, 1, 0);

header.setValue("CONTENT-TYPE", "text/xml; charset=\"utf-8\"" );
header.setValue("SOAPACTION" , QString( "\"%1#GetConnectionInfo\"" )
.arg( m_sNamespace ));
header.setValue("SOAPACTION",
QString("\"%1#%2\"").arg(m_sNamespace).arg(sMethod));

// --------------------------------------------------------------
// Build request payload
Expand All @@ -176,18 +213,20 @@ QDomDocument SOAPClient::SendSOAPRequest( const QString &sMethod,
QByteArray aBuffer;
QTextStream os( &aBuffer );

os.setCodec("UTF-8");

os << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n";
os << "<s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n";
os << "<s:Envelope "
" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\""
" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n";
os << " <s:Body>\r\n";
os << " <u:" << sMethod << " xmlns:u=\"" << m_sNamespace << "\">\r\n";

// --------------------------------------------------------------
// Add parameters from list
// --------------------------------------------------------------

for ( QStringMap::iterator it = list.begin();
it != list.end();
++it )
for (QStringMap::iterator it = list.begin(); it != list.end(); ++it)
{
os << " <" << it.key() << ">";
os << HTTPRequest::Encode( *it );
Expand All @@ -204,32 +243,48 @@ QDomDocument SOAPClient::SendSOAPRequest( const QString &sMethod,
// Perform Request
// --------------------------------------------------------------

QBuffer buff( &aBuffer );

QString sXml = HttpComms::postHttp( url,
&header,
(QIODevice *)&buff,
10000, // ms
3, // retries
0, // redirects
false, // allow gzip
NULL, // login
bInQtThread );
QBuffer buff(&aBuffer);

VERBOSE(VB_UPNP|VB_EXTRA,
QString("SOAPClient(%1) sending:\n").arg(url.toString()) +
header.toString() +
QString("\n%1\n").arg(aBuffer.constData()));

QString sXml = HttpComms::postHttp(
url,
&header,
&buff, // QIODevice*
10000, // ms -- Technically we should allow 30,000 ms per spec
3, // retries
0, // redirects
false, // allow gzip
NULL, // login
bInQtThread,
QString() // userAgent, UPnP/1.0 very strict on format if set
);

// --------------------------------------------------------------
// Parse response
// --------------------------------------------------------------

VERBOSE(VB_UPNP|VB_EXTRA, "SOAPClient response:\n" +QString("%1\n")
.arg(sXml));

// TODO handle timeout without response correctly.

list.clear();

QDomDocument xmlResult;
QDomDocument doc;

if ( !doc.setContent( sXml, true, &sErrDesc, &nErrCode ))
if (!doc.setContent(sXml, true, &sErrDesc, &nErrCode))
{
VERBOSE( VB_UPNP, QString( "MythXMLClient::SendSOAPRequest( %1 ) - Invalid response from %2" )
.arg( sMethod )
.arg( url.toString() ));
VERBOSE(VB_UPNP,
QString("MythXMLClient::SendSOAPRequest( %1 ) - "
"Invalid response from %2")
.arg(sMethod).arg(url.toString()) +
QString("%1: %2").arg(nErrCode).arg(sErrDesc));

return QDomDocument();
}

Expand All @@ -238,7 +293,8 @@ QDomDocument SOAPClient::SendSOAPRequest( const QString &sMethod,
// --------------------------------------------------------------

QString sResponseName = sMethod + "Response";
QDomNodeList oNodeList = doc.elementsByTagNameNS( m_sNamespace, sResponseName );
QDomNodeList oNodeList =
doc.elementsByTagNameNS(m_sNamespace, sResponseName);

if (oNodeList.count() > 0)
{
Expand Down
70 changes: 38 additions & 32 deletions mythtv/libs/libmythupnp/soapclient.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,44 +30,50 @@

#include "httpcomms.h"
#include "upnputil.h"
#include "upnpexp.h"

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
//
//
//
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////

class SOAPClient
/// \brief Subclass SOAPClient to perform actions using the command URL.
class UPNP_PUBLIC SOAPClient
{
protected:

QString m_sNamespace;
QString m_sControlPath;
QUrl m_url;

public:

SOAPClient( const QUrl &url,
const QString &sNamespace,
const QString &sControlPath );
virtual ~SOAPClient();
public:
SOAPClient(const QUrl &url,
const QString &sNamespace,
const QString &sControlPath);
/// \brief Empty SOAPClient constructor. When this is used, Init()
/// Must be called before SendSOAPRequest().
SOAPClient() {}
virtual ~SOAPClient() {}

protected:
bool Init(const QUrl &url,
const QString &sNamespace,
const QString &sControlPath);

int GetNodeValue( QDomNode &node, const QString &sName, int nDefault );
bool GetNodeValue( QDomNode &node, const QString &sName, bool bDefault );
QString GetNodeValue( QDomNode &node, const QString &sName, const QString &sDefault );
protected:
int GetNodeValue(const QDomNode &node,
const QString &sName,
int nDefault) const;
bool GetNodeValue(const QDomNode &node,
const QString &sName,
bool bDefault) const;
QString GetNodeValue(const QDomNode &node,
const QString &sName,
const QString &sDefault) const;

QDomNode FindNode( const QString &sName , QDomNode &baseNode );
QDomNode FindNode( QStringList &sParts, QDomNode &curNode );
QDomNode FindNode(const QString &sName,
const QDomNode &baseNode) const;

QDomDocument SendSOAPRequest( const QString &sMethod,
QStringMap &list,
int &nErrCode,
QString &sErrDesc,
bool bInQtThread );
QDomDocument SendSOAPRequest(const QString &sMethod,
QStringMap &list,
int &nErrCode,
QString &sErrDesc,
bool bInQtThread);
private:
QDomNode FindNodeInternal(QStringList &sParts,
const QDomNode &curNode) const;
protected:
QUrl m_url;
QString m_sNamespace;
QString m_sControlPath;
};

#endif
Expand Down
130 changes: 51 additions & 79 deletions mythtv/libs/libmythupnp/ssdp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@
//
//////////////////////////////////////////////////////////////////////////////

#include <algorithm>

#include "upnp.h"
#include "mythverbose.h"
#include "mythlogging.h"

#include "upnptasksearch.h"
#include "upnptaskcache.h"

#include "multicast.h"
#include "broadcast.h"
#include "mmulticastsocketdevice.h"
#include "mbroadcastsocketdevice.h"

#include <QRegExp>
#include <QStringList>
Expand All @@ -46,6 +48,7 @@

// We're creating this class immediately so it will always be available.

static QMutex g_pSSDPCreationLock;
SSDP* SSDP::g_pSSDP = NULL;

/////////////////////////////////////////////////////////////////////////////
Expand All @@ -54,6 +57,7 @@ SSDP* SSDP::g_pSSDP = NULL;

SSDP* SSDP::Instance()
{
QMutexLocker locker(&g_pSSDPCreationLock);
return g_pSSDP ? g_pSSDP : (g_pSSDP = new SSDP());
}

Expand All @@ -75,12 +79,15 @@ SSDP::SSDP() :

Configuration *pConfig = UPnp::GetConfiguration();

m_nPort = pConfig->GetValue( "UPnP/SSDP/Port" , SSDP_PORT );
m_nSearchPort = pConfig->GetValue( "UPnP/SSDP/SearchPort", SSDP_SEARCHPORT );
m_nPort = pConfig->GetValue("UPnP/SSDP/Port" , SSDP_PORT );
m_nSearchPort = pConfig->GetValue("UPnP/SSDP/SearchPort", SSDP_SEARCHPORT);

m_Sockets[ SocketIdx_Search ] = new MSocketDevice( MSocketDevice::Datagram );
m_Sockets[ SocketIdx_Multicast ] = new QMulticastSocket( SSDP_GROUP, m_nPort );
m_Sockets[ SocketIdx_Broadcast ] = new QBroadcastSocket( "255.255.255.255", m_nPort );
m_Sockets[ SocketIdx_Search ] =
new MMulticastSocketDevice();
m_Sockets[ SocketIdx_Multicast ] =
new MMulticastSocketDevice(SSDP_GROUP, m_nPort);
m_Sockets[ SocketIdx_Broadcast ] =
new MBroadcastSocketDevice("255.255.255.255", m_nPort);

m_Sockets[ SocketIdx_Search ]->setBlocking( false );
m_Sockets[ SocketIdx_Multicast ]->setBlocking( false );
Expand All @@ -89,7 +96,7 @@ SSDP::SSDP() :
// Setup SearchSocket
QHostAddress ip4addr( QHostAddress::Any );

m_Sockets[ SocketIdx_Search ]->bind( ip4addr , m_nSearchPort );
m_Sockets[ SocketIdx_Search ]->bind( ip4addr , m_nSearchPort );
m_Sockets[ SocketIdx_Search ]->bind( QHostAddress::Any, m_nSearchPort );

// ----------------------------------------------------------------------
Expand All @@ -98,7 +105,7 @@ SSDP::SSDP() :

start();

VERBOSE(VB_UPNP, "SSDP::ctor - SSDP Thread Started." );
VERBOSE(VB_UPNP, "SSDP::ctor - SSDP Thread Starting soon" );
}

/////////////////////////////////////////////////////////////////////////////
Expand All @@ -125,6 +132,7 @@ SSDP::~SSDP()
}
}

QMutexLocker locker(&g_pSSDPCreationLock);
g_pSSDP = NULL;

VERBOSE(VB_UPNP, "SSDP::Destructor - SSDP Thread Terminated." );
Expand Down Expand Up @@ -194,15 +202,20 @@ void SSDP::DisableNotifications()
/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////

void SSDP::PerformSearch( const QString &sST )
void SSDP::PerformSearch(const QString &sST, uint timeout_secs)
{
QString rRequest = QString( "M-SEARCH * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1900\r\n"
"MAN: \"ssdp:discover\"\r\n"
"MX: 2\r\n"
"ST: %1\r\n"
"\r\n" ).arg( sST );
timeout_secs = std::max(std::min(timeout_secs, 5U), 1U);
QString rRequest = QString(
"M-SEARCH * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1900\r\n"
"MAN: \"ssdp:discover\"\r\n"
"MX: %1\r\n"
"ST: %2\r\n"
"\r\n")
.arg(timeout_secs).arg(sST);

VERBOSE(VB_UPNP, QString("\n\n%1\n").arg(rRequest));

QByteArray sRequest = rRequest.toUtf8();

MSocketDevice *pSocket = m_Sockets[ SocketIdx_Search ];
Expand Down Expand Up @@ -736,68 +749,27 @@ void SSDPExtension::GetFile( HTTPRequest *pRequest, QString sFileName )

void SSDPExtension::GetDeviceList( HTTPRequest *pRequest )
{
SSDPCache* pCache = SSDPCache::Instance();
int nCount = 0;
NameValues list;

VERBOSE( VB_UPNP, "SSDPExtension::GetDeviceList" );

pCache->Lock();

QString sXML = "";
QTextStream os( &sXML, QIODevice::WriteOnly );

for (SSDPCacheEntriesMap::Iterator it = pCache->Begin();
it != pCache->End();
++it )
{
SSDPCacheEntries *pEntries = *it;

if (pEntries != NULL)
{
os << "<Device uri='" << it.key() << "'>" << endl;

pEntries->Lock();

EntryMap *pMap = pEntries->GetEntryMap();

for (EntryMap::Iterator itEntry = pMap->begin();
itEntry != pMap->end();
++itEntry )
{

DeviceLocation *pEntry = *itEntry;

if (pEntry != NULL)
{
nCount++;

pEntry->AddRef();

os << "<Service usn='" << pEntry->m_sUSN
<< "' expiresInSecs='" << pEntry->ExpiresInSecs()
<< "' url='" << pEntry->m_sLocation << "' />" << endl;

pEntry->Release();
}
}

os << "</Device>" << endl;

pEntries->Unlock();
}
}
os << flush;

list.push_back( NameValue("DeviceCount" , pCache->Count() ));
list.push_back( NameValue("DevicesAllocated" , SSDPCacheEntries::g_nAllocated));
list.push_back( NameValue("CacheEntriesFound" , nCount ));
list.push_back( NameValue("CacheEntriesAllocated", DeviceLocation::g_nAllocated ));
list.push_back( NameValue("DeviceList" , sXML ));

pCache->Unlock();

pRequest->FormatActionResponse( list );
VERBOSE(VB_UPNP, "SSDPExtension::GetDeviceList");

QString sXML;
QTextStream os(&sXML, QIODevice::WriteOnly);

uint nDevCount, nEntryCount;
SSDPCache::Instance()->OutputXML(os, &nDevCount, &nEntryCount);

NameValues list;
list.push_back(
NameValue("DeviceCount", (int)nDevCount));
list.push_back(
NameValue("DevicesAllocated", SSDPCacheEntries::g_nAllocated));
list.push_back(
NameValue("CacheEntriesFound", (int)nEntryCount));
list.push_back(
NameValue("CacheEntriesAllocated", DeviceLocation::g_nAllocated));
list.push_back(
NameValue("DeviceList", sXML));

pRequest->FormatActionResponse(list);

pRequest->m_eResponseType = ResponseTypeXML;
pRequest->m_nResponseStatus = 200;
Expand Down
17 changes: 10 additions & 7 deletions mythtv/libs/libmythupnp/ssdp.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,20 +124,23 @@ class UPNP_PUBLIC SSDP : public QThread

void RequestTerminate(void);

void PerformSearch( const QString &sST );
void PerformSearch(const QString &sST, uint timeout_secs = 2);

void EnableNotifications ( int nServicePort );
void DisableNotifications();

// ------------------------------------------------------------------

static void AddListener ( QObject *listener ) { SSDPCache::Instance()->addListener ( listener ); }
static void RemoveListener( QObject *listener ) { SSDPCache::Instance()->removeListener( listener ); }

static SSDPCacheEntries *Find( const QString &sURI ) { return SSDPCache::Instance()->Find( sURI ); }
static DeviceLocation *Find( const QString &sURI,
const QString &sUSN ) { return SSDPCache::Instance()->Find( sURI, sUSN ); }
static void AddListener(QObject *listener)
{ SSDPCache::Instance()->addListener(listener); }
static void RemoveListener(QObject *listener)
{ SSDPCache::Instance()->removeListener(listener); }

static SSDPCacheEntries *Find(const QString &sURI)
{ return SSDPCache::Instance()->Find(sURI); }
static DeviceLocation *Find(const QString &sURI,
const QString &sUSN)
{ return SSDPCache::Instance()->Find( sURI, sUSN ); }
};

/////////////////////////////////////////////////////////////////////////////
Expand Down
390 changes: 214 additions & 176 deletions mythtv/libs/libmythupnp/ssdpcache.cpp

Large diffs are not rendered by default.

62 changes: 33 additions & 29 deletions mythtv/libs/libmythupnp/ssdpcache.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,50 +32,50 @@

#include "upnpdevice.h"

typedef QMap< QString, DeviceLocation * > EntryMap; // Key == Unique Service Name (USN)
/// Key == Unique Service Name (USN)
typedef QMap< QString, DeviceLocation * > EntryMap;

/////////////////////////////////////////////////////////////////////////////
// QDict Implementation that uses RefCounted pointers
/////////////////////////////////////////////////////////////////////////////

class SSDPCacheEntries : public RefCounted
class UPNP_PUBLIC SSDPCacheEntries : public RefCounted
{
public:
protected:
/// Destructor protected to enforce Release method usage
virtual ~SSDPCacheEntries();

static int g_nAllocated; // Debugging only
public:
SSDPCacheEntries();

protected:
void Clear(void);
uint Count(void) const
{ QMutexLocker locker(&m_mutex); return m_mapEntries.size(); }
void Insert(const QString &sUSN, DeviceLocation *pEntry);
void Remove(const QString &sUSN);
uint RemoveStale(const TaskTime &ttNow);

QMutex m_mutex;
EntryMap m_mapEntries;
DeviceLocation *Find(const QString &sUSN);

protected:
DeviceLocation *GetFirst(void);

// Destructor protected to force use of Release Method
void GetEntryMap(EntryMap&);

virtual ~SSDPCacheEntries();
QTextStream &OutputXML(QTextStream &os, uint *pnEntryCount = NULL) const;
void Dump(uint &nEntryCount) const;

public:
static QString GetNormalizedUSN(const QString &sUSN);

SSDPCacheEntries();
public:
static int g_nAllocated; // Debugging only

void Lock () { m_mutex.lock(); }
void Unlock () { m_mutex.unlock(); }

void Clear ( );

int Count ( ) { return m_mapEntries.size(); }

DeviceLocation *Find ( const QString &sUSN );
void Insert ( const QString &sUSN, DeviceLocation *pEntry );
void Remove ( const QString &sUSN );
int RemoveStale( const TaskTime &ttNow );

// Must Lock/Unlock when using.
EntryMap *GetEntryMap() { return &m_mapEntries; }
protected:
mutable QMutex m_mutex;
EntryMap m_mapEntries;
};

typedef QMap< QString, SSDPCacheEntries * > SSDPCacheEntriesMap; // Key == Service Type URI
/// Key == Service Type URI
typedef QMap< QString, SSDPCacheEntries * > SSDPCacheEntriesMap;

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
Expand All @@ -96,7 +96,7 @@ class UPNP_PUBLIC SSDPCache : public QObject,

protected:

QMutex m_mutex;
mutable QMutex m_mutex;
SSDPCacheEntriesMap m_cache;

void NotifyAdd ( const QString &sURI,
Expand Down Expand Up @@ -134,7 +134,11 @@ class UPNP_PUBLIC SSDPCache : public QObject,
void Remove ( const QString &sURI, const QString &sUSN );
int RemoveStale( );

void Dump ( );
void Dump (void);

QTextStream &OutputXML(QTextStream &os,
uint *pnDevCount = NULL,
uint *pnEntryCount = NULL) const;

SSDPCacheEntries *Find( const QString &sURI );
DeviceLocation *Find( const QString &sURI, const QString &sUSN );
Expand Down
7 changes: 2 additions & 5 deletions mythtv/libs/libmythupnp/upnp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,9 @@
//
//////////////////////////////////////////////////////////////////////////////

#include "upnp.h"
#include "mythverbose.h"

#include "upnptaskcache.h"
#include "multicast.h"
#include "broadcast.h"
#include "mythverbose.h"
#include "upnp.h"

//////////////////////////////////////////////////////////////////////////////
// Global/Class Static variables
Expand Down
139 changes: 130 additions & 9 deletions mythtv/libs/libmythupnp/upnpdevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,10 +286,8 @@ void UPnpDeviceDesc::ProcessDeviceList( QDomNode oListNode,
}
}

/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////

/// \brief Sets sValue param to n.firstChild().toText().nodeValue(), iff
/// neither n.isNull() nor n.firstChild().toText().isNull() is true.
void UPnpDeviceDesc::SetStrValue( const QDomNode &n, QString &sValue )
{
if (!n.isNull())
Expand All @@ -301,10 +299,8 @@ void UPnpDeviceDesc::SetStrValue( const QDomNode &n, QString &sValue )
}
}

/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////

/// \brief Sets nValue param to n.firstChild().toText().nodeValue(), iff
/// neither n.isNull() nor n.firstChild().toText().isNull() is true.
void UPnpDeviceDesc::SetNumValue( const QDomNode &n, int &nValue )
{
if (!n.isNull())
Expand Down Expand Up @@ -366,7 +362,8 @@ void UPnpDeviceDesc::GetValidXML(
#endif

os << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:mythtv=\"mythtv.org\">\n"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\" "
" xmlns:mythtv=\"mythtv.org\">\n"
"<specVersion>\n"
"<major>1</major>\n"
"<minor>0</minor>\n"
Expand Down Expand Up @@ -720,3 +717,127 @@ QString UPnpDeviceDesc::GetHostName()

return m_sHostName;
}

/////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
/////////////////////////////////////////////////////////////////////////////

UPnpService UPnpDevice::GetService(const QString &urn, bool *found) const
{
UPnpService srv;

bool done = false;

UPnpServiceList::const_iterator it = m_listServices.begin();
for (; it != m_listServices.end(); ++it)
{
if ((*it)->m_sServiceType == urn)
{
srv = **it;
done = true;
break;
}
}

if (!done)
{
UPnpDeviceList::const_iterator dit = m_listDevices.begin();
for (; dit != m_listDevices.end() && !done; ++dit)
srv = (*dit)->GetService(urn, &done);
}

if (found)
*found = done;

return srv;
}

QString UPnpDevice::toString(uint padding) const
{
QString ret =
QString("UPnP Device\n"
"===========\n"
"deviceType: %1\n"
"friendlyName: %2\n"
"manufacturer: %3\n"
"manufacturerURL: %4\n"
"modelDescription: %5\n"
"modelName: %6\n"
"modelNumber: %7\n"
"modelURL: %8\n")
.arg(m_sDeviceType )
.arg(m_sFriendlyName )
.arg(m_sManufacturer )
.arg(m_sManufacturerURL )
.arg(m_sModelDescription)
.arg(m_sModelName )
.arg(m_sModelNumber )
.arg(m_sModelURL ) +
QString("serialNumber: %1\n"
"UPC: %2\n"
"presentationURL: %3\n"
"UDN: %4\n")
.arg(m_sSerialNumber )
.arg(m_sUPC )
.arg(m_sPresentationURL )
.arg(m_sUDN );

if (!m_lstExtra.empty())
{
NameValues::const_iterator it = m_lstExtra.begin();
ret += "Extra key value pairs\n";
for (; it != m_lstExtra.end(); ++it)
{
ret += (*it).sName;
ret += ":";
int int_padding = 18 - ((*it).sName.length() + 1);
for (int i = 0; i < int_padding; i++)
ret += " ";
ret += QString("%1\n").arg((*it).sValue);
}
}

if (!m_listIcons.empty())
{
ret += "Icon List:\n";
UPnpIconList::const_iterator it = m_listIcons.begin();
for (; it != m_listIcons.end(); ++it)
ret += (*it)->toString(padding+2) + "\n";
}

if (!m_listServices.empty())
{
ret += "Service List:\n";
UPnpServiceList::const_iterator it = m_listServices.begin();
for (; it != m_listServices.end(); ++it)
ret += (*it)->toString(padding+2) + "\n";
}

if (!m_listDevices.empty())
{
ret += "Device List:\n";
UPnpDeviceList::const_iterator it = m_listDevices.begin();
for (; it != m_listDevices.end(); ++it)
ret += (*it)->toString(padding+2) + "\n";
ret += "\n";
}

// remove trailing newline
if (ret.right(1)=="\n")
ret = ret.left(ret.length()-1);

// add any padding as necessary
if (padding)
{
QString pad;
for (uint i = 0; i < padding; i++)
pad += " ";
ret = pad + ret.replace("\n", QString("\n%1").arg(pad));
}

return ret;
}
72 changes: 53 additions & 19 deletions mythtv/libs/libmythupnp/upnpdevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
#define __UPNPDEVICE_H__

#include <QDomDocument>
#include <QUrl>
#include <QHash>
#include <QUrl>

#include "compat.h"
#include "upnpexp.h"
Expand Down Expand Up @@ -54,29 +54,51 @@ typedef QList< UPnpIcon* > UPnpIconList;

class UPNP_PUBLIC UPnpIcon
{
public:

QString m_sMimeType;
int m_nWidth;
int m_nHeight;
int m_nDepth;
QString m_sURL;

UPnpIcon() : m_nWidth ( 0 ), m_nHeight( 0 ), m_nDepth( 0 ) {}
public:
QString m_sURL;
QString m_sMimeType;
int m_nWidth;
int m_nHeight;
int m_nDepth;

UPnpIcon() : m_nWidth(0), m_nHeight(0), m_nDepth(0) {}

QString toString(uint padding) const
{
QString pad;
for (uint i = 0; i < padding; i++)
pad += " ";
return QString("%0Icon %1 %2x%3^%4 %5")
.arg(pad).arg(m_sURL).arg(m_nWidth).arg(m_nHeight)
.arg(m_nDepth).arg(m_sMimeType);
}
};

/////////////////////////////////////////////////////////////////////////////

class UPNP_PUBLIC UPnpService
{
public:
QString m_sServiceType;
QString m_sServiceId;
QString m_sSCPDURL;
QString m_sControlURL;
QString m_sEventSubURL;

UPnpService() {}
public:
QString m_sServiceType;
QString m_sServiceId;
QString m_sSCPDURL;
QString m_sControlURL;
QString m_sEventSubURL;

UPnpService() {}

QString toString(uint padding) const
{
QString pad;
for (uint i = 0; i < padding; i++)
pad += " ";
return
QString("%0Service %1\n").arg(pad).arg(m_sServiceType) +
QString("%0 id: %1\n").arg(pad).arg(m_sServiceId) +
QString("%0 SCPD URL: %1\n").arg(pad).arg(m_sSCPDURL) +
QString("%0 Control URL: %1\n").arg(pad).arg(m_sControlURL) +
QString("%0 Event Sub URL: %1").arg(pad).arg(m_sEventSubURL);
}
};

/////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -159,6 +181,10 @@ class UPNP_PUBLIC UPnpDevice
map["UPC"] = m_sUPC;
map["protocolversion"] = m_protocolVersion;
}

UPnpService GetService(const QString &urn, bool *found = NULL) const;

QString toString(uint padding = 0) const;
};


Expand Down Expand Up @@ -278,7 +304,7 @@ class UPNP_PUBLIC DeviceLocation : public RefCounted

// ==================================================================

int ExpiresInSecs()
int ExpiresInSecs(void) const
{
TaskTime ttNow;
gettimeofday( (&ttNow), NULL );
Expand Down Expand Up @@ -345,6 +371,14 @@ class UPNP_PUBLIC DeviceLocation : public RefCounted

return pDevice->m_rootDevice.m_securityPin;
}

QString toString() const
{
return QString("\nURI:%1\nUSN:%2\nDeviceXML:%3\n"
"Expires:%4\nMythTV PIN:%5")
.arg(m_sURI).arg(m_sUSN).arg(m_sLocation)
.arg(ExpiresInSecs()).arg(m_sSecurityPin);
}
};

#endif
19 changes: 19 additions & 0 deletions mythtv/libs/libmythupnp/upnpserviceimpl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "upnpserviceimpl.h"
#include "upnpdevice.h"

/// Creates a UPnpService and adds it to the UPnpDevice's list of services.
void UPnpServiceImpl::RegisterService(UPnpDevice *pDevice)
{
if (pDevice != NULL)
{
UPnpService *pService = new UPnpService();

pService->m_sServiceType = GetServiceType();
pService->m_sServiceId = GetServiceId();
pService->m_sSCPDURL = GetServiceDescURL();
pService->m_sControlURL = GetServiceControlURL();
pService->m_sEventSubURL = GetServiceEventURL();

pDevice->m_listServices.push_back(pService);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//////////////////////////////////////////////////////////////////////////////
// Program Name: UPnpImpl.h
// Program Name: upnpserviceimpl.h
// Created : Jan 15, 2007
//
// Purpose : UPnp Device Description parser/generator
Expand All @@ -21,41 +21,35 @@
//
//////////////////////////////////////////////////////////////////////////////

#ifndef __UPNPIMPL_H__
#define __UPNPIMPL_H__
#ifndef _UPNPIMPL_H_
#define _UPNPIMPL_H_

#include "upnpdevice.h"
#include "upnpexp.h"

class UPnpServiceImpl
#include <QString>

class UPnpDevice;

/// Base class for services we offer to other UPnP devices.
class UPNP_PUBLIC UPnpServiceImpl
{
protected:

virtual QString GetServiceType () = 0;
virtual QString GetServiceId () = 0;
virtual QString GetServiceControlURL() = 0;
virtual QString GetServiceDescURL () = 0;
virtual QString GetServiceEventURL () { return ""; }

public:

UPnpServiceImpl() {}
virtual ~UPnpServiceImpl() {}

void RegisterService( UPnpDevice *pDevice )
{
if (pDevice != NULL)
{
UPnpService *pService = new UPnpService();

pService->m_sServiceType = GetServiceType();
pService->m_sServiceId = GetServiceId();
pService->m_sSCPDURL = GetServiceDescURL();
pService->m_sControlURL = GetServiceControlURL();
pService->m_sEventSubURL = GetServiceEventURL();

pDevice->m_listServices.append( pService );
}
}
public:
UPnpServiceImpl() {}
virtual ~UPnpServiceImpl() {}

void RegisterService(UPnpDevice *device);

protected:
/// Provices the schema urn
virtual QString GetServiceType(void) = 0;
/// Provides the device specific urn
virtual QString GetServiceId(void) = 0;
/// Provices the base URL for control commands
virtual QString GetServiceControlURL(void) = 0;
/// Provices the URL of the service description XML
virtual QString GetServiceDescURL(void) = 0;
/// Provides the URL of the event portion of the service
virtual QString GetServiceEventURL(void) { return QString(); }
};

#endif
#endif /// _UPNPIMPL_H_
3 changes: 3 additions & 0 deletions mythtv/libs/libmythupnp/upnptaskcache.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
#ifndef __UPNPTASKCACHE_H__
#define __UPNPTASKCACHE_H__

#include <QString>

#include "mythverbose.h"
#include "upnp.h"

/////////////////////////////////////////////////////////////////////////////
Expand Down
37 changes: 15 additions & 22 deletions mythtv/libs/libmythupnp/upnptasknotify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,10 @@
#include <QFile>

// MythTV headers
#include "mmulticastsocketdevice.h"
#include "mythverbose.h"
#include "upnp.h"
#include "multicast.h"
#include "broadcast.h"

#include "compat.h"
#include "upnp.h"

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -69,9 +67,9 @@ UPnpNotifyTask::~UPnpNotifyTask()
//
/////////////////////////////////////////////////////////////////////////////

void UPnpNotifyTask::SendNotifyMsg( QMulticastSocket *pSocket,
QString sNT,
QString sUDN )
void UPnpNotifyTask::SendNotifyMsg( MSocketDevice *pSocket,
QString sNT,
QString sUDN )
{
QString sUSN;

Expand All @@ -94,8 +92,8 @@ void UPnpNotifyTask::SendNotifyMsg( QMulticastSocket *pSocket,
.arg( m_nMaxAge );

VERBOSE(VB_UPNP, QString("UPnpNotifyTask::SendNotifyMsg : %1:%2 : %3 : %4")
.arg( pSocket->m_address.toString() )
.arg( pSocket->m_port )
.arg( pSocket->address().toString() )
.arg( pSocket->port() )
.arg( sNT )
.arg( sUSN ));

Expand All @@ -115,7 +113,7 @@ void UPnpNotifyTask::SendNotifyMsg( QMulticastSocket *pSocket,
if ((*it).isEmpty())
{
VERBOSE(VB_GENERAL,
"UPnpNotifyTask::SendNotifyMsg - NULL in m_addressList");
"UPnpNotifyTask::SendNotifyMsg - NULL in address list");
continue;
}

Expand All @@ -128,8 +126,8 @@ void UPnpNotifyTask::SendNotifyMsg( QMulticastSocket *pSocket,
QString sHeader = QString( "NOTIFY * HTTP/1.1\r\n"
"HOST: %1:%2\r\n"
"LOCATION: http://%3:%4/getDeviceDesc\r\n" )
.arg( pSocket->m_address.toString() )
.arg( pSocket->m_port )
.arg( pSocket->address().toString() )
.arg( pSocket->port() )
.arg( ipaddress )
.arg( m_nServicePort);

Expand All @@ -140,9 +138,9 @@ void UPnpNotifyTask::SendNotifyMsg( QMulticastSocket *pSocket,
// Send Packet to Socket (Send same packet twice)
// ------------------------------------------------------------------

pSocket->writeBlock( scPacket, scPacket.length(), pSocket->m_address, pSocket->m_port );
pSocket->writeBlock( scPacket, scPacket.length(), pSocket->address(), pSocket->port() );
usleep( rand() % 250000 );
pSocket->writeBlock( scPacket, scPacket.length(), pSocket->m_address, pSocket->m_port );
pSocket->writeBlock( scPacket, scPacket.length(), pSocket->address(), pSocket->port() );
}
}

Expand All @@ -154,9 +152,8 @@ void UPnpNotifyTask::SendNotifyMsg( QMulticastSocket *pSocket,

void UPnpNotifyTask::Execute( TaskQueue *pQueue )
{

QMulticastSocket *pMulticast = new QMulticastSocket(SSDP_GROUP, SSDP_PORT);
// QSocketDevice *pBroadcast = new QBroadcastSocket( "255.255.255.255", SSDP_PORT );
MSocketDevice *pMulticast = new MMulticastSocketDevice(
SSDP_GROUP, SSDP_PORT);

// ----------------------------------------------------------------------
// Must send rootdevice Notification for first device.
Expand All @@ -165,24 +162,20 @@ void UPnpNotifyTask::Execute( TaskQueue *pQueue )
UPnpDevice &device = UPnp::g_UPnpDeviceDesc.m_rootDevice;

SendNotifyMsg( pMulticast, "upnp:rootdevice", device.GetUDN() );
// SendNotifyMsg( pBroadcast, "upnp:rootdevice", device.GetUDN() );

// ----------------------------------------------------------------------
// Process rest of notifications
// ----------------------------------------------------------------------

ProcessDevice( pMulticast, &device );
// ProcessDevice( pBroadcast, &device );

// ----------------------------------------------------------------------
// Clean up and reshedule task if needed (timeout = m_nMaxAge / 2).
// ----------------------------------------------------------------------

delete pMulticast;
// delete pBroadcast;

pMulticast = NULL;
// pBroadcast = NULL;

m_mutex.lock();

Expand All @@ -198,7 +191,7 @@ void UPnpNotifyTask::Execute( TaskQueue *pQueue )
/////////////////////////////////////////////////////////////////////////////

void UPnpNotifyTask::ProcessDevice(
QMulticastSocket *pSocket, UPnpDevice *pDevice)
MSocketDevice *pSocket, UPnpDevice *pDevice)
{
// ----------------------------------------------------------------------
// Loop for each device and send the 2 required messages
Expand Down
9 changes: 5 additions & 4 deletions mythtv/libs/libmythupnp/upnptasknotify.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@
#include <QMutex>

// MythTV headers
#include <msocketdevice.h>
#include "multicast.h"
#include "compat.h"

class MSocketDevice;
class UPnpDevice;

/////////////////////////////////////////////////////////////////////////////
// Typedefs
/////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -77,8 +78,8 @@ class UPnpNotifyTask : public Task

virtual ~UPnpNotifyTask();

void ProcessDevice( QMulticastSocket *pSocket, UPnpDevice *pDevice );
void SendNotifyMsg( QMulticastSocket *pSocket, QString sNT, QString sUDN );
void ProcessDevice( MSocketDevice *pSocket, UPnpDevice *pDevice );
void SendNotifyMsg( MSocketDevice *pSocket, QString sNT, QString sUDN );

public:

Expand Down
34 changes: 19 additions & 15 deletions mythtv/programs/mythbackend/httpstatus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,14 +290,17 @@ void HttpStatus::FillStatusXML( QDomDocument *pDoc )
QDomElement frontends = pDoc->createElement("Frontends");
root.appendChild(frontends);

SSDPCacheEntries* fes = SSDP::Find("urn:schemas-mythtv-org:service:MythFrontend:1");
SSDPCacheEntries *fes = SSDP::Find(
"urn:schemas-mythtv-org:service:MythFrontend:1");
if (fes)
{
fes->AddRef();
fes->Lock();
EntryMap* map = fes->GetEntryMap();
frontends.setAttribute( "count", map->size() );
QMapIterator< QString, DeviceLocation * > i(*map);
EntryMap map;
fes->GetEntryMap(map);
fes->Release();
fes = NULL;

frontends.setAttribute( "count", map.size() );
QMapIterator< QString, DeviceLocation * > i(map);
while (i.hasNext())
{
i.next();
Expand All @@ -306,9 +309,8 @@ void HttpStatus::FillStatusXML( QDomDocument *pDoc )
QUrl url(i.value()->m_sLocation);
fe.setAttribute("name", url.host());
fe.setAttribute("url", url.toString(QUrl::RemovePath));
i.value()->Release();
}
fes->Unlock();
fes->Release();
}

// Other backends
Expand All @@ -331,18 +333,21 @@ void HttpStatus::FillStatusXML( QDomDocument *pDoc )
mbe.setAttribute("url" , masterip + ":" + masterport);
}

SSDPCacheEntries* sbes = SSDP::Find("urn:schemas-mythtv-org:device:SlaveMediaServer:1");
SSDPCacheEntries *sbes = SSDP::Find(
"urn:schemas-mythtv-org:device:SlaveMediaServer:1");
if (sbes)
{

QString ipaddress = QString();
if (!UPnp::g_IPAddrList.isEmpty())
ipaddress = UPnp::g_IPAddrList.at(0);

sbes->AddRef();
sbes->Lock();
EntryMap* map = sbes->GetEntryMap();
QMapIterator< QString, DeviceLocation * > i(*map);
EntryMap map;
sbes->GetEntryMap(map);
sbes->Release();
sbes = NULL;

QMapIterator< QString, DeviceLocation * > i(map);
while (i.hasNext())
{
i.next();
Expand All @@ -356,9 +361,8 @@ void HttpStatus::FillStatusXML( QDomDocument *pDoc )
mbe.setAttribute("name", url.host());
mbe.setAttribute("url" , url.toString(QUrl::RemovePath));
}
i.value()->Release();
}
sbes->Unlock();
sbes->Release();
}

backends.setAttribute("count", numbes);
Expand Down
36 changes: 21 additions & 15 deletions mythtv/programs/mythfrontend/mediarenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,22 +94,25 @@ class MythFrontendStatus : public HttpServerExtension
stream
<< " <div class=\"content\">\r\n"
<< " <h2 class=\"status\">Other Frontends</h2>\r\n";
cache->AddRef();
cache->Lock();
EntryMap* map = cache->GetEntryMap();
QMapIterator< QString, DeviceLocation * > i(*map);

EntryMap map;
cache->GetEntryMap(map);
cache->Release();
cache = NULL;

QMapIterator< QString, DeviceLocation * > i(map);
while (i.hasNext())
{
i.next();
QUrl url(i.value()->m_sLocation);
if (url.host() != ipaddress)
{
stream << "<br />" << url.host() << "&nbsp(<a href=\""
<< url.toString(QUrl::RemovePath) << "\">Status page</a>)\r\n";
<< url.toString(QUrl::RemovePath)
<< "\">Status page</a>)\r\n";
}
i.value()->Release();
}
cache->Unlock();
cache->Release();
stream << " </div>\r\n";
}

Expand All @@ -125,19 +128,22 @@ class MythFrontendStatus : public HttpServerExtension
cache = SSDP::Find("urn:schemas-mythtv-org:device:SlaveMediaServer:1");
if (cache)
{
cache->AddRef();
cache->Lock();
EntryMap* map = cache->GetEntryMap();
QMapIterator< QString, DeviceLocation * > i(*map);
EntryMap map;
cache->GetEntryMap(map);
cache->Release();
cache = NULL;

QMapIterator< QString, DeviceLocation * > i(map);
while (i.hasNext())
{
i.next();
QUrl url(i.value()->m_sLocation);
stream << "<br />" << "Slave: " << url.host() << "&nbsp(<a href=\""
<< url.toString(QUrl::RemovePath) << "\">Status page</a>)\r\n";
stream << "<br />" << "Slave: " << url.host()
<< "&nbsp(<a href=\""
<< url.toString(QUrl::RemovePath)
<< "\">Status page</a>)\r\n";
i.value()->Release();
}
cache->Unlock();
cache->Release();
}

stream
Expand Down