MythTV  0.26-pre
eventing.cpp
Go to the documentation of this file.
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 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends