|
MythTV
0.26-pre
|
00001 00002 // Program Name: eventing.cpp 00003 // Created : Dec. 22, 2006 00004 // 00005 // Purpose : uPnp Eventing Base Class Implementation 00006 // 00007 // Copyright (c) 2006 David Blain <dblain@mythtv.org> 00008 // 00009 // Licensed under the GPL v2 or later, see COPYING for details 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 // -=>TODO: Should use an Atomic increment... 00093 // need to research available functions. 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 // -=>TODO: Should use an Atomic decrement... 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 // -=>TODO: This isn't very efficient... Need to find out if we can make 00140 // this something unique, other than root. 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 // Use PostProcessing Hook to perform Initial Notification 00183 // to make sure they receive it AFTER the subscription results 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 // Validate Header Values... 00211 // ---------------------------------------------------------------------- 00212 00213 // -=>TODO: Need to add support for more than one CallBack URL. 00214 // -=>TODO: Need to handle Timeout header 00215 00216 if ( sCallBack.length() != 0 ) 00217 { 00218 // ------------------------------------------------------------------ 00219 // New Subscription 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 // Process Subscription 00233 // ---------------------------------------------------------------------- 00234 00235 // -=>TODO: Temp code until support for multiple callbacks are supported. 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 // Use PostProcess Hook to Send Initial FULL Notification... 00250 // *** Must send this response first then notify. 00251 00252 m_pInitializeSubscriber = pInfo; 00253 pRequest->m_pPostProcess = (IPostProcess *)this; 00254 00255 } 00256 else 00257 { 00258 // ------------------------------------------------------------------ 00259 // Renewal 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 { // This should never happen, but if someone inserted bad data... 00330 ++it; 00331 continue; 00332 } 00333 00334 if (tt < (*it)->ttExpires) 00335 { 00336 // Subscription not expired yet. Send event notification. 00337 NotifySubscriber(*it); 00338 ++it; 00339 } 00340 else 00341 { 00342 // Time to expire this subscription. Remove subscriber from list. 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 // Build Body... Only send if there are changes 00367 // ---------------------------------------------------------------------- 00368 00369 uint nCount = BuildNotifyBody(tsBody, pInfo->ttLastNotified); 00370 if (nCount) 00371 { 00372 00373 // -=>TODO: Need to add support for more than one CallBack URL. 00374 00375 QByteArray *pBuffer = new QByteArray(); // UPnpEventTask will delete this pointer. 00376 QTextStream tsMsg( pBuffer, QIODevice::WriteOnly ); 00377 00378 tsMsg.setCodec(QTextCodec::codecForName("UTF-8")); 00379 00380 // ---------------------------------------------------------------------- 00381 // Build Message Header 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 // Add new EventTask to the TaskQueue to do the actual sending. 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 // Update the subscribers Key & last Notified fields 00416 // ------------------------------------------------------------------ 00417 00418 pInfo->IncrementKey(); 00419 00420 gettimeofday( (&pInfo->ttLastNotified), NULL ); 00421 } 00422 } 00423
1.7.6.1