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