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  foreach (auto prop, 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  foreach (auto & subscriber, 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 
QStringList GetBasePaths() override
Definition: eventing.cpp:129
QMutex m_mutex
Definition: eventing.h:258
void NotifySubscriber(SubscriberInfo *pInfo)
Definition: eventing.cpp:355
short m_nHoldCount
Definition: eventing.h:265
QString m_sBaseUrl
Definition: httprequest.h:125
static TaskQueue * Instance()
Definition: taskqueue.cpp:58
Definition: upnp.h:99
struct timeval TaskTime
Definition: httpserver.h:45
unsigned long IncrementKey()
Definition: eventing.h:53
short ReleaseEvents()
Definition: eventing.cpp:111
~Eventing() override
Definition: eventing.cpp:75
void HandleUnsubscribe(HTTPRequest *pRequest)
Definition: eventing.cpp:288
QString m_sMethod
Definition: httprequest.h:127
QStringMap m_mapRespHeaders
Definition: httprequest.h:151
QString GetRequestHeader(const QString &sKey, QString sDefault)
long m_nResponseStatus
Definition: httprequest.h:150
static void FormatErrorResponse(HTTPRequest *pRequest, UPnPResultCode eCode, const QString &sMsg="")
Definition: upnp.cpp:281
IPostProcess * m_pPostProcess
Definition: httprequest.h:157
unsigned int uint
Definition: compat.h:140
unsigned short m_nKey
Definition: eventing.h:67
void AddTask(long msec, Task *pTask)
Definition: taskqueue.cpp:170
bool ProcessRequest(HTTPRequest *pRequest) override
Definition: eventing.cpp:141
void HandleSubscribe(HTTPRequest *pRequest)
Definition: eventing.cpp:189
void Notify() override
Definition: eventing.cpp:318
Eventing(const QString &sExtensionName, QString sEventMethodName, const QString &sSharePath)
Definition: eventing.cpp:60
unsigned long m_nDuration
Definition: eventing.h:68
TaskTime m_ttLastNotified
Definition: eventing.h:63
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QString m_sUUID
Definition: eventing.h:65
Subscribers m_Subscribers
Definition: eventing.h:261
int m_nSubscriptionDuration
Definition: eventing.h:263
uint BuildNotifyBody(QTextStream &ts, TaskTime ttLastNotified) const
Definition: eventing.cpp:28
HttpResponseType m_eResponseType
Definition: httprequest.h:147
short HoldEvents()
Definition: eventing.cpp:86
QString m_sEventMethodName
Definition: eventing.h:260
HttpRequestType m_eType
Definition: httprequest.h:118
void ExecutePostProcess() override
Definition: eventing.cpp:172
SubscriberInfo * m_pInitializeSubscriber
Definition: eventing.h:267