00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00012
00013 #include <cmath>
00014
00015 #include <QTextCodec>
00016 #include <QTextStream>
00017 #include <QStringList>
00018
00019 #include "upnp.h"
00020 #include "eventing.h"
00021 #include "upnptaskevent.h"
00022 #include "mythlogging.h"
00023
00025
00027
00028 uint StateVariables::BuildNotifyBody(
00029 QTextStream &ts, TaskTime ttLastNotified) const
00030 {
00031 uint nCount = 0;
00032
00033 ts << "<?xml version=\"1.0\"?>" << endl
00034 << "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">" << endl;
00035
00036 SVMap::const_iterator it = m_map.begin();
00037 for (; it != m_map.end(); ++it)
00038 {
00039 if ( ttLastNotified < (*it)->m_ttLastChanged )
00040 {
00041 nCount++;
00042
00043 ts << "<e:property>" << endl;
00044 ts << "<" << (*it)->m_sName << ">";
00045 ts << (*it)->ToString();
00046 ts << "</" << (*it)->m_sName << ">";
00047 ts << "</e:property>" << endl;
00048 }
00049 }
00050
00051 ts << "</e:propertyset>" << endl;
00052 ts << flush;
00053
00054 return nCount;
00055 }
00056
00058
00060
00061 Eventing::Eventing(const QString &sExtensionName,
00062 const QString &sEventMethodName,
00063 const QString sSharePath) :
00064 HttpServerExtension(sExtensionName, sSharePath),
00065 m_sEventMethodName(sEventMethodName),
00066 m_nSubscriptionDuration(
00067 UPnp::GetConfiguration()->GetValue("UPnP/SubscriptionDuration", 1800)),
00068 m_nHoldCount(0),
00069 m_pInitializeSubscriber(NULL)
00070 {
00071 m_sEventMethodName.detach();
00072 }
00073
00075
00077
00078 Eventing::~Eventing()
00079 {
00080 Subscribers::iterator it = m_Subscribers.begin();
00081 for (; it != m_Subscribers.end(); ++it)
00082 delete *it;
00083 m_Subscribers.clear();
00084 }
00085
00087
00089
00090 inline short Eventing::HoldEvents()
00091 {
00092
00093
00094
00095 short nVal;
00096
00097 m_mutex.lock();
00098 bool err = (m_nHoldCount >= 127);
00099 nVal = (m_nHoldCount++);
00100 m_mutex.unlock();
00101
00102 if (err)
00103 {
00104 LOG(VB_GENERAL, LOG_ERR, "Exceeded maximum guarranteed range of "
00105 "m_nHoldCount short [-128..127]");
00106 LOG(VB_GENERAL, LOG_ERR,
00107 "UPnP may not exhibit strange behavior or crash mythtv");
00108 }
00109
00110 return nVal;
00111 }
00112
00114
00116
00117 inline short Eventing::ReleaseEvents()
00118 {
00119
00120
00121 short nVal;
00122
00123 m_mutex.lock();
00124 nVal = (m_nHoldCount--);
00125 m_mutex.unlock();
00126
00127 if (nVal == 0)
00128 Notify();
00129
00130 return nVal;
00131 }
00132
00134
00136
00137 QStringList Eventing::GetBasePaths()
00138 {
00139
00140
00141
00142 return QStringList( "/" );
00143 }
00144
00146
00148
00149 bool Eventing::ProcessRequest( HTTPRequest *pRequest )
00150 {
00151 if (pRequest)
00152 {
00153 if ( pRequest->m_sBaseUrl != "/" )
00154 return false;
00155
00156 if ( pRequest->m_sMethod != m_sEventMethodName )
00157 return false;
00158
00159 LOG(VB_UPNP, LOG_INFO, QString("Eventing::ProcessRequest - Method (%1)")
00160 .arg(pRequest->m_sMethod ));
00161
00162 switch( pRequest->m_eType )
00163 {
00164 case RequestTypeSubscribe : HandleSubscribe ( pRequest ); break;
00165 case RequestTypeUnsubscribe : HandleUnsubscribe ( pRequest ); break;
00166 default:
00167 UPnp::FormatErrorResponse( pRequest, UPnPResult_InvalidAction );
00168 break;
00169 }
00170 }
00171
00172 return( true );
00173
00174 }
00175
00177
00179
00180 void Eventing::ExecutePostProcess( )
00181 {
00182
00183
00184
00185 if (m_pInitializeSubscriber != NULL)
00186 {
00187 NotifySubscriber( m_pInitializeSubscriber );
00188
00189 m_pInitializeSubscriber = NULL;
00190 }
00191 }
00192
00194
00196
00197 void Eventing::HandleSubscribe( HTTPRequest *pRequest )
00198 {
00199 pRequest->m_eResponseType = ResponseTypeXML;
00200 pRequest->m_nResponseStatus = 412;
00201
00202 QString sCallBack = pRequest->GetHeaderValue( "CALLBACK", "" );
00203 QString sNT = pRequest->GetHeaderValue( "NT" , "" );
00204 QString sTimeout = pRequest->GetHeaderValue( "TIMOUT" , "" );
00205 QString sSID = pRequest->GetHeaderValue( "SID" , "" );
00206
00207 SubscriberInfo *pInfo = NULL;
00208
00209
00210
00211
00212
00213
00214
00215
00216 if ( sCallBack.length() != 0 )
00217 {
00218
00219
00220
00221
00222 if ( sSID.length() != 0 )
00223 {
00224 pRequest->m_nResponseStatus = 400;
00225 return;
00226 }
00227
00228 if ( sNT != "upnp:event" )
00229 return;
00230
00231
00232
00233
00234
00235
00236
00237 sCallBack = sCallBack.mid( 1, sCallBack.indexOf(">") - 1);
00238
00239 pInfo = new SubscriberInfo( sCallBack, m_nSubscriptionDuration );
00240
00241 Subscribers::iterator it = m_Subscribers.find(pInfo->sUUID);
00242 if (it != m_Subscribers.end())
00243 {
00244 delete *it;
00245 m_Subscribers.erase(it);
00246 }
00247 m_Subscribers[pInfo->sUUID] = pInfo;
00248
00249
00250
00251
00252 m_pInitializeSubscriber = pInfo;
00253 pRequest->m_pPostProcess = (IPostProcess *)this;
00254
00255 }
00256 else
00257 {
00258
00259
00260
00261
00262 if ( sSID.length() != 0 )
00263 {
00264 sSID = sSID.mid( 5 );
00265 pInfo = m_Subscribers[sSID];
00266 }
00267
00268 }
00269
00270 if (pInfo != NULL)
00271 {
00272 pRequest->m_mapRespHeaders[ "SID" ] = QString( "uuid:%1" )
00273 .arg( pInfo->sUUID );
00274
00275 pRequest->m_mapRespHeaders[ "TIMEOUT"] = QString( "Second-%1" )
00276 .arg( pInfo->nDuration );
00277
00278 pRequest->m_nResponseStatus = 200;
00279
00280 }
00281
00282 }
00283
00285
00287
00288 void Eventing::HandleUnsubscribe( HTTPRequest *pRequest )
00289 {
00290 pRequest->m_eResponseType = ResponseTypeXML;
00291 pRequest->m_nResponseStatus = 412;
00292
00293 QString sCallBack = pRequest->GetHeaderValue( "CALLBACK", "" );
00294 QString sNT = pRequest->GetHeaderValue( "NT" , "" );
00295 QString sSID = pRequest->GetHeaderValue( "SID" , "" );
00296
00297 if ((sCallBack.length() != 0) || (sNT.length() != 0))
00298 {
00299 pRequest->m_nResponseStatus = 400;
00300 return;
00301 }
00302
00303 sSID = sSID.mid( 5 );
00304
00305 Subscribers::iterator it = m_Subscribers.find(sSID);
00306 if (it != m_Subscribers.end())
00307 {
00308 delete *it;
00309 m_Subscribers.erase(it);
00310 pRequest->m_nResponseStatus = 200;
00311 }
00312 }
00313
00315
00317
00318 void Eventing::Notify()
00319 {
00320 TaskTime tt;
00321 gettimeofday( (&tt), NULL );
00322
00323 m_mutex.lock();
00324
00325 Subscribers::iterator it = m_Subscribers.begin();
00326 while (it != m_Subscribers.end())
00327 {
00328 if (!(*it))
00329 {
00330 ++it;
00331 continue;
00332 }
00333
00334 if (tt < (*it)->ttExpires)
00335 {
00336
00337 NotifySubscriber(*it);
00338 ++it;
00339 }
00340 else
00341 {
00342
00343 delete *it;
00344 it = m_Subscribers.erase(it);
00345 }
00346 }
00347
00348 m_mutex.unlock();
00349 }
00350
00352
00354
00355 void Eventing::NotifySubscriber( SubscriberInfo *pInfo )
00356 {
00357 if (pInfo == NULL)
00358 return;
00359
00360 QByteArray aBody;
00361 QTextStream tsBody( &aBody, QIODevice::WriteOnly );
00362
00363 tsBody.setCodec(QTextCodec::codecForName("UTF-8"));
00364
00365
00366
00367
00368
00369 uint nCount = BuildNotifyBody(tsBody, pInfo->ttLastNotified);
00370 if (nCount)
00371 {
00372
00373
00374
00375 QByteArray *pBuffer = new QByteArray();
00376 QTextStream tsMsg( pBuffer, QIODevice::WriteOnly );
00377
00378 tsMsg.setCodec(QTextCodec::codecForName("UTF-8"));
00379
00380
00381
00382
00383
00384 int nPort = (pInfo->qURL.port()>=0) ? pInfo->qURL.port() : 80;
00385 QString sHost = QString( "%1:%2" ).arg( pInfo->qURL.host() )
00386 .arg( nPort );
00387
00388 tsMsg << "NOTIFY " << pInfo->qURL.path() << " HTTP/1.1\r\n";
00389 tsMsg << "HOST: " << sHost << "\r\n";
00390 tsMsg << "CONTENT-TYPE: \"text/xml\"\r\n";
00391 tsMsg << "Content-Length: " << QString::number( aBody.size() ) << "\r\n";
00392 tsMsg << "NT: upnp:event\r\n";
00393 tsMsg << "NTS: upnp:propchange\r\n";
00394 tsMsg << "SID: uuid:" << pInfo->sUUID << "\r\n";
00395 tsMsg << "SEQ: " << QString::number( pInfo->nKey ) << "\r\n";
00396 tsMsg << "\r\n";
00397 tsMsg << aBody;
00398 tsMsg << flush;
00399
00400
00401
00402
00403
00404 LOG(VB_UPNP, LOG_INFO,
00405 QString("UPnp::Eventing::NotifySubscriber( %1 ) : %2 Variables")
00406 .arg( sHost ).arg(nCount));
00407
00408 UPnpEventTask *pEventTask =
00409 new UPnpEventTask(QHostAddress( pInfo->qURL.host() ),
00410 nPort, pBuffer );
00411
00412 TaskQueue::Instance()->AddTask( 250, pEventTask );
00413
00414
00415
00416
00417
00418 pInfo->IncrementKey();
00419
00420 gettimeofday( (&pInfo->ttLastNotified), NULL );
00421 }
00422 }
00423