MythTV  master
eventing.cpp
Go to the documentation of this file.
1 // Program Name: eventing.cpp
3 // Created : Dec. 22, 2006
4 //
5 // Purpose : uPnp Eventing Base Class Implementation
6 //
7 // Copyright (c) 2006 David Blain <dblain@mythtv.org>
8 //
9 // Licensed under the GPL v2 or later, see COPYING for details
10 //
12 
13 #include <cmath>
14 
15 #include <QStringList>
16 #include <QTextCodec>
17 #include <QTextStream>
18 
19 #include "upnp.h"
20 #include "eventing.h"
21 #include "upnptaskevent.h"
22 #include "mythlogging.h"
23 
25 //
27 
29  QTextStream &ts, TaskTime ttLastNotified) const
30 {
31  uint nCount = 0;
32 
33  ts << "<?xml version=\"1.0\"?>" << endl
34  << "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">" << endl;
35 
36  for (auto *prop : qAsConst(m_map))
37  {
38  if ( ttLastNotified < prop->m_ttLastChanged )
39  {
40  nCount++;
41 
42  ts << "<e:property>" << endl;
43  ts << "<" << prop->m_sName << ">";
44  ts << prop->ToString();
45  ts << "</" << prop->m_sName << ">";
46  ts << "</e:property>" << endl;
47  }
48  }
49 
50  ts << "</e:propertyset>" << endl;
51  ts << flush;
52 
53  return nCount;
54 }
55 
57 //
59 
60 Eventing::Eventing(const QString &sExtensionName,
61  QString sEventMethodName,
62  const QString &sSharePath) :
63  HttpServerExtension(sExtensionName, sSharePath),
64  m_sEventMethodName(std::move(sEventMethodName)),
65  m_nSubscriptionDuration(
66  UPnp::GetConfiguration()->GetValue("UPnP/SubscriptionDuration", 1800))
67 {
69 }
70 
72 //
74 
76 {
77  for (const auto *subscriber : qAsConst(m_subscribers))
78  delete subscriber;
79  m_subscribers.clear();
80 }
81 
83 //
85 
86 inline short Eventing::HoldEvents()
87 {
88  // -=>TODO: Should use an Atomic increment...
89  // need to research available functions.
90 
91  m_mutex.lock();
92  bool err = (m_nHoldCount >= 127);
93  short nVal = (m_nHoldCount++);
94  m_mutex.unlock();
95 
96  if (err)
97  {
98  LOG(VB_GENERAL, LOG_ERR, "Exceeded maximum guarranteed range of "
99  "m_nHoldCount short [-128..127]");
100  LOG(VB_GENERAL, LOG_ERR,
101  "UPnP may not exhibit strange behavior or crash mythtv");
102  }
103 
104  return nVal;
105 }
106 
108 //
110 
112 {
113  // -=>TODO: Should use an Atomic decrement...
114 
115  m_mutex.lock();
116  short nVal = (m_nHoldCount--);
117  m_mutex.unlock();
118 
119  if (nVal == 0)
120  Notify();
121 
122  return nVal;
123 }
124 
126 //
128 
130 {
131  // -=>TODO: This isn't very efficient... Need to find out if we can make
132  // this something unique, other than root.
133 
134  return QStringList( "/" );
135 }
136 
138 //
140 
142 {
143  if (pRequest)
144  {
145  if ( pRequest->m_sBaseUrl != "/" )
146  return false;
147 
148  if ( pRequest->m_sMethod != m_sEventMethodName )
149  return false;
150 
151  LOG(VB_UPNP, LOG_INFO, QString("Eventing::ProcessRequest - Method (%1)")
152  .arg(pRequest->m_sMethod ));
153 
154  switch( pRequest->m_eType )
155  {
156  case RequestTypeSubscribe : HandleSubscribe ( pRequest ); break;
157  case RequestTypeUnsubscribe : HandleUnsubscribe ( pRequest ); break;
158  default:
160  break;
161  }
162  }
163 
164  return( true );
165 
166 }
167 
169 //
171 
173 {
174  // Use PostProcessing Hook to perform Initial Notification
175  // to make sure they receive it AFTER the subscription results
176 
177  if (m_pInitializeSubscriber != nullptr)
178  {
180 
181  m_pInitializeSubscriber = nullptr;
182  }
183 }
184 
186 //
188 
190 {
191  pRequest->m_eResponseType = ResponseTypeXML;
192  pRequest->m_nResponseStatus = 412;
193 
194  QString sCallBack = pRequest->GetRequestHeader( "CALLBACK", "" );
195  QString sNT = pRequest->GetRequestHeader( "NT" , "" );
196  QString sTimeout = pRequest->GetRequestHeader( "TIMEOUT" , "" );
197  QString sSID = pRequest->GetRequestHeader( "SID" , "" );
198 
199  SubscriberInfo *pInfo = nullptr;
200 
201  // ----------------------------------------------------------------------
202  // Validate Header Values...
203  // ----------------------------------------------------------------------
204 
205  // -=>TODO: Need to add support for more than one CallBack URL.
206 
207  if ( sCallBack.length() != 0 )
208  {
209  // ------------------------------------------------------------------
210  // New Subscription
211  // ------------------------------------------------------------------
212 
213  if ( sSID.length() != 0 )
214  {
215  pRequest->m_nResponseStatus = 400;
216  return;
217  }
218 
219  if ( sNT != "upnp:event" )
220  return;
221 
222  // ----------------------------------------------------------------------
223  // Process Subscription
224  // ----------------------------------------------------------------------
225 
226  // -=>TODO: Temp code until support for multiple callbacks are supported.
227 
228  sCallBack = sCallBack.mid( 1, sCallBack.indexOf(">") - 1);
229 
230  int nDuration = m_nSubscriptionDuration;
231  if ( sTimeout.startsWith("Second-") )
232  {
233  bool ok = false;
234  int nValue = sTimeout.section("-", 1).toInt(&ok);
235  if (ok)
236  nDuration = nValue;
237  }
238 
239  pInfo = new SubscriberInfo( sCallBack, nDuration );
240 
241  Subscribers::iterator it = m_subscribers.find(pInfo->m_sUUID);
242  if (it != m_subscribers.end())
243  {
244  delete *it;
245  m_subscribers.erase(it);
246  }
247  m_subscribers[pInfo->m_sUUID] = pInfo;
248 
249  // Use PostProcess Hook to Send Initial FULL Notification...
250  // *** Must send this response first then notify.
251 
252  m_pInitializeSubscriber = pInfo;
253  pRequest->m_pPostProcess = (IPostProcess *)this;
254 
255  }
256  else
257  {
258  // ------------------------------------------------------------------
259  // Renewal
260  // ------------------------------------------------------------------
261 
262  if ( sSID.length() != 0 )
263  {
264  sSID = sSID.mid( 5 );
265  pInfo = m_subscribers[sSID];
266  }
267 
268  }
269 
270  if (pInfo != nullptr)
271  {
272  pRequest->m_mapRespHeaders[ "SID" ] = QString( "uuid:%1" )
273  .arg( pInfo->m_sUUID );
274 
275  pRequest->m_mapRespHeaders[ "TIMEOUT"] = QString( "Second-%1" )
276  .arg( pInfo->m_nDuration );
277 
278  pRequest->m_nResponseStatus = 200;
279 
280  }
281 
282 }
283 
285 //
287 
289 {
290  pRequest->m_eResponseType = ResponseTypeXML;
291  pRequest->m_nResponseStatus = 412;
292 
293  QString sCallBack = pRequest->GetRequestHeader( "CALLBACK", "" );
294  QString sNT = pRequest->GetRequestHeader( "NT" , "" );
295  QString sSID = pRequest->GetRequestHeader( "SID" , "" );
296 
297  if ((sCallBack.length() != 0) || (sNT.length() != 0))
298  {
299  pRequest->m_nResponseStatus = 400;
300  return;
301  }
302 
303  sSID = sSID.mid( 5 );
304 
305  Subscribers::iterator it = m_subscribers.find(sSID);
306  if (it != m_subscribers.end())
307  {
308  delete *it;
309  m_subscribers.erase(it);
310  pRequest->m_nResponseStatus = 200;
311  }
312 }
313 
315 //
317 
319 {
320  TaskTime tt;
321  gettimeofday( (&tt), nullptr );
322 
323  m_mutex.lock();
324 
325  Subscribers::iterator it = m_subscribers.begin();
326  while (it != m_subscribers.end())
327  {
328  if (!(*it))
329  { // This should never happen, but if someone inserted bad data...
330  ++it;
331  continue;
332  }
333 
334  if (tt < (*it)->m_ttExpires)
335  {
336  // Subscription not expired yet. Send event notification.
337  NotifySubscriber(*it);
338  ++it;
339  }
340  else
341  {
342  // Time to expire this subscription. Remove subscriber from list.
343  delete *it;
344  it = m_subscribers.erase(it);
345  }
346  }
347 
348  m_mutex.unlock();
349 }
350 
352 //
354 
356 {
357  if (pInfo == nullptr)
358  return;
359 
360  QByteArray aBody;
361  QTextStream tsBody( &aBody, QIODevice::WriteOnly );
362 
363  tsBody.setCodec(QTextCodec::codecForName("UTF-8"));
364 
365  // ----------------------------------------------------------------------
366  // Build Body... Only send if there are changes
367  // ----------------------------------------------------------------------
368 
369  uint nCount = BuildNotifyBody(tsBody, pInfo->m_ttLastNotified);
370  if (nCount)
371  {
372 
373  // -=>TODO: Need to add support for more than one CallBack URL.
374 
375  auto *pBuffer = new QByteArray(); // UPnpEventTask will delete this pointer.
376  QTextStream tsMsg( pBuffer, QIODevice::WriteOnly );
377 
378  tsMsg.setCodec(QTextCodec::codecForName("UTF-8"));
379 
380  // ----------------------------------------------------------------------
381  // Build Message Header
382  // ----------------------------------------------------------------------
383 
384  int nPort = (pInfo->m_qURL.port()>=0) ? pInfo->m_qURL.port() : 80;
385  QString sHost = QString( "%1:%2" ).arg( pInfo->m_qURL.host() )
386  .arg( nPort );
387 
388  tsMsg << "NOTIFY " << pInfo->m_qURL.path() << " HTTP/1.1\r\n";
389  tsMsg << "HOST: " << sHost << "\r\n";
390  tsMsg << "CONTENT-TYPE: \"text/xml\"\r\n";
391  tsMsg << "Content-Length: " << QString::number( aBody.size() ) << "\r\n";
392  tsMsg << "NT: upnp:event\r\n";
393  tsMsg << "NTS: upnp:propchange\r\n";
394  tsMsg << "SID: uuid:" << pInfo->m_sUUID << "\r\n";
395  tsMsg << "SEQ: " << QString::number( pInfo->m_nKey ) << "\r\n";
396  tsMsg << "\r\n";
397  tsMsg << aBody;
398  tsMsg << flush;
399 
400  // ------------------------------------------------------------------
401  // Add new EventTask to the TaskQueue to do the actual sending.
402  // ------------------------------------------------------------------
403 
404  LOG(VB_UPNP, LOG_INFO,
405  QString("UPnp::Eventing::NotifySubscriber( %1 ) : %2 Variables")
406  .arg( sHost ).arg(nCount));
407 
408  auto *pEventTask = new UPnpEventTask(QHostAddress(pInfo->m_qURL.host()),
409  nPort, pBuffer);
410 
411  TaskQueue::Instance()->AddTask( 250, pEventTask );
412 
413  pEventTask->DecrRef();
414 
415  // ------------------------------------------------------------------
416  // Update the subscribers Key & last Notified fields
417  // ------------------------------------------------------------------
418 
419  pInfo->IncrementKey();
420 
421  gettimeofday( (&pInfo->m_ttLastNotified), nullptr );
422  }
423 }
424 
Eventing::ExecutePostProcess
void ExecutePostProcess() override
Definition: eventing.cpp:172
HTTPRequest::m_sBaseUrl
QString m_sBaseUrl
Definition: httprequest.h:125
Eventing::ReleaseEvents
short ReleaseEvents()
Definition: eventing.cpp:111
HttpServerExtension::m_nSupportedMethods
uint m_nSupportedMethods
Definition: httpserver.h:81
HTTPRequest
Definition: httprequest.h:108
Eventing::m_subscribers
Subscribers m_subscribers
Definition: eventing.h:261
Eventing::GetBasePaths
QStringList GetBasePaths() override
Definition: eventing.cpp:129
Eventing::m_sEventMethodName
QString m_sEventMethodName
Definition: eventing.h:260
HTTPRequest::m_sMethod
QString m_sMethod
Definition: httprequest.h:127
StateVariables::m_map
SVMap m_map
Definition: eventing.h:176
SubscriberInfo::m_nKey
unsigned short m_nKey
Definition: eventing.h:67
arg
arg(title).arg(filename).arg(doDelete))
TaskQueue::AddTask
void AddTask(long msec, Task *pTask)
Definition: taskqueue.cpp:170
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
Eventing::ProcessRequest
bool ProcessRequest(HTTPRequest *pRequest) override
Definition: eventing.cpp:141
TaskTime
struct timeval TaskTime
Definition: httpserver.h:45
HTTPRequest::GetRequestHeader
QString GetRequestHeader(const QString &sKey, QString sDefault)
Definition: httprequest.cpp:1158
RequestTypeSubscribe
@ RequestTypeSubscribe
Definition: httprequest.h:57
Eventing::m_pInitializeSubscriber
SubscriberInfo * m_pInitializeSubscriber
Definition: eventing.h:267
RequestTypeUnsubscribe
@ RequestTypeUnsubscribe
Definition: httprequest.h:58
UPnp::FormatErrorResponse
static void FormatErrorResponse(HTTPRequest *pRequest, UPnPResultCode eCode, const QString &sMsg="")
Definition: upnp.cpp:280
HTTPRequest::m_mapRespHeaders
QStringMap m_mapRespHeaders
Definition: httprequest.h:151
SubscriberInfo::m_nDuration
unsigned long m_nDuration
Definition: eventing.h:68
Eventing::HandleSubscribe
void HandleSubscribe(HTTPRequest *pRequest)
Definition: eventing.cpp:189
eventing.h
upnp.h
HTTPRequest::m_nResponseStatus
long m_nResponseStatus
Definition: httprequest.h:150
mythlogging.h
Eventing::HandleUnsubscribe
void HandleUnsubscribe(HTTPRequest *pRequest)
Definition: eventing.cpp:288
upnptaskevent.h
SubscriberInfo
Definition: eventing.h:31
TaskQueue::Instance
static TaskQueue * Instance()
Definition: taskqueue.cpp:58
HTTPRequest::m_pPostProcess
IPostProcess * m_pPostProcess
Definition: httprequest.h:157
SubscriberInfo::m_sUUID
QString m_sUUID
Definition: eventing.h:65
Eventing::NotifySubscriber
void NotifySubscriber(SubscriberInfo *pInfo)
Definition: eventing.cpp:355
Eventing::Eventing
Eventing(const QString &sExtensionName, QString sEventMethodName, const QString &sSharePath)
Definition: eventing.cpp:60
SubscriberInfo::IncrementKey
unsigned long IncrementKey()
Definition: eventing.h:53
ResponseTypeXML
@ ResponseTypeXML
Definition: httprequest.h:76
uint
unsigned int uint
Definition: compat.h:140
SubscriberInfo::m_qURL
QUrl m_qURL
Definition: eventing.h:66
Eventing::Notify
void Notify() override
Definition: eventing.cpp:318
SubscriberInfo::m_ttLastNotified
TaskTime m_ttLastNotified
Definition: eventing.h:63
IPostProcess
Definition: httprequest.h:97
UPnPResult_InvalidAction
@ UPnPResult_InvalidAction
Definition: upnp.h:40
UPnp
Definition: upnp.h:100
Eventing::m_mutex
QMutex m_mutex
Definition: eventing.h:258
HTTPRequest::m_eResponseType
HttpResponseType m_eResponseType
Definition: httprequest.h:147
UPnpEventTask
Definition: upnptaskevent.h:28
StateVariables::BuildNotifyBody
uint BuildNotifyBody(QTextStream &ts, TaskTime ttLastNotified) const
Definition: eventing.cpp:28
HttpServerExtension
Definition: httpserver.h:72
Eventing::m_nSubscriptionDuration
int m_nSubscriptionDuration
Definition: eventing.h:263
Eventing::m_nHoldCount
short m_nHoldCount
Definition: eventing.h:265
HTTPRequest::m_eType
HttpRequestType m_eType
Definition: httprequest.h:118
Eventing::HoldEvents
short HoldEvents()
Definition: eventing.cpp:86
Eventing::~Eventing
~Eventing() override
Definition: eventing.cpp:75