MythTV master
ssdpcache.cpp
Go to the documentation of this file.
1
2// Program Name: ssdpcache.cpp
3// Created : Jan. 8, 2007
4//
5// Purpose :
6//
7// Copyright (c) 2007 David Blain <dblain@mythtv.org>
8//
9// Licensed under the GPL v2 or later, see LICENSE for details
10//
12#include "ssdpcache.h"
13
14#include <chrono>
15
19
20#include "taskqueue.h"
21
23
24int SSDPCacheEntries::g_nAllocated = 0; // Debugging only
25
28//
29// SSDPCacheEntries Implementation
30//
33
35{
36 g_nAllocated++; // Should be atomic increment
37}
38
40{
41 Clear();
42 g_nAllocated--; // Should be atomic decrement
43}
44
47{
48 QMutexLocker locker(&m_mutex);
49
50 for (auto *const entry : std::as_const(m_mapEntries))
51 {
52 if (entry)
53 entry->DecrRef();
54 }
55
56 m_mapEntries.clear();
57}
58
62{
63 QMutexLocker locker(&m_mutex);
64
65 EntryMap::iterator it = m_mapEntries.find(GetNormalizedUSN(sUSN));
66 DeviceLocation *pEntry = (it != m_mapEntries.end()) ? *it : nullptr;
67 if (pEntry)
68 pEntry->IncrRef();
69
70 return pEntry;
71}
72
76{
77 QMutexLocker locker(&m_mutex);
78 if (m_mapEntries.empty())
79 return nullptr;
80 DeviceLocation *loc = *m_mapEntries.begin();
81 loc->IncrRef();
82 return loc;
83}
84
88{
89 QMutexLocker locker(&m_mutex);
90
91 for (auto it = m_mapEntries.cbegin(); it != m_mapEntries.cend(); ++it)
92 {
93 (*it)->IncrRef();
94 map.insert(it.key(), *it);
95 }
96}
97
99void SSDPCacheEntries::Insert(const QString &sUSN, DeviceLocation *pEntry)
100{
101 QMutexLocker locker(&m_mutex);
102
103 pEntry->IncrRef();
104
105 // Since insert overwrites anything already there
106 // we need to see if the key already exists and release
107 // it's reference if it does.
108
109 QString usn = GetNormalizedUSN(sUSN);
110
111 EntryMap::iterator it = m_mapEntries.find(usn);
112 if ((it != m_mapEntries.end()) && (*it != nullptr))
113 (*it)->DecrRef();
114
115 m_mapEntries[usn] = pEntry;
116
117 LOG(VB_UPNP, LOG_INFO, QString("SSDP Cache adding USN: %1 Location %2")
118 .arg(pEntry->m_sUSN, pEntry->m_sLocation));
119}
120
122void SSDPCacheEntries::Remove( const QString &sUSN )
123{
124 QMutexLocker locker(&m_mutex);
125
126 QString usn = GetNormalizedUSN(sUSN);
127 EntryMap::iterator it = m_mapEntries.find(usn);
128 if (it != m_mapEntries.end())
129 {
130 if (*it)
131 {
132 LOG(VB_UPNP, LOG_INFO,
133 QString("SSDP Cache removing USN: %1 Location %2")
134 .arg((*it)->m_sUSN, (*it)->m_sLocation));
135 (*it)->DecrRef();
136 }
137
138 // -=>TODO: Need to somehow call SSDPCache::NotifyRemove
139
140 m_mapEntries.erase(it);
141 }
142}
143
145uint SSDPCacheEntries::RemoveStale(const std::chrono::microseconds ttNow)
146{
147 QMutexLocker locker(&m_mutex);
148 uint nCount = 0;
149
150 EntryMap::iterator it = m_mapEntries.begin();
151 while (it != m_mapEntries.end())
152 {
153 if (*it == nullptr)
154 {
155 it = m_mapEntries.erase(it);
156 }
157 else if ((*it)->m_ttExpires < ttNow)
158 {
159 // Note: locking is not required above since we hold
160 // one reference to each entry and are holding m_mutex.
161 (*it)->DecrRef();
162
163 // -=>TODO: Need to somehow call SSDPCache::NotifyRemove
164
165 it = m_mapEntries.erase(it);
166 nCount++;
167 }
168 else
169 {
170 ++it;
171 }
172 }
173
174 return nCount;
175}
176
179 QTextStream &os, uint *pnEntryCount) const
180{
181 QMutexLocker locker(&m_mutex);
182
183 for (auto *entry : std::as_const(m_mapEntries))
184 {
185 if (entry == nullptr)
186 continue;
187
188 // Note: IncrRef,DecrRef not required since SSDPCacheEntries
189 // holds one reference to each entry and we are holding m_mutex.
190 os << "<Service usn='" << entry->m_sUSN
191 << "' expiresInSecs='" << entry->ExpiresInSecs().count()
192 << "' url='" << entry->m_sLocation << "' />" << Qt::endl;
193
194 if (pnEntryCount != nullptr)
195 (*pnEntryCount)++;
196 }
197
198 return os;
199}
200
202void SSDPCacheEntries::Dump(uint &nEntryCount) const
203{
204 QMutexLocker locker(&m_mutex);
205
206 for (auto *entry : std::as_const(m_mapEntries))
207 {
208 if (entry == nullptr)
209 continue;
210
211 // Note: IncrRef,DecrRef not required since SSDPCacheEntries
212 // holds one reference to each entry and we are holding m_mutex.
213 LOG(VB_UPNP, LOG_DEBUG, QString(" * \t\t%1\t | %2\t | %3 ")
214 .arg(entry->m_sUSN) .arg(entry->ExpiresInSecs().count())
215 .arg(entry->m_sLocation));
216
217 nEntryCount++;
218 }
219}
220
223QString SSDPCacheEntries::GetNormalizedUSN(const QString &sUSN)
224{
225 int uuid_end_loc = sUSN.indexOf(":",5);
226 if (uuid_end_loc > 0)
227 return sUSN.left(uuid_end_loc).toLower() + sUSN.mid(uuid_end_loc);
228 return sUSN;
229}
230
231class SSDPCacheTask : public Task
232{
233 protected:
234
235 std::chrono::milliseconds m_nInterval {30s}; // Number of ms between executing.
236 int m_nExecuteCount {0}; // Used for debugging.
237
238 // Destructor protected to force use of Release Method
239
240 ~SSDPCacheTask() override = default;
241
242 public:
243
244 SSDPCacheTask() : Task("SSDPCacheTask")
245 {
246 m_nInterval = 30s;
247// TODO: Rework when separating upnp/ssdp stack
248// XmlConfiguration().GetDuration<std::chrono::seconds>("UPnP/SSDP/CacheInterval", 30s);
249 }
250
251 QString Name() override // Task
252 {
253 return( "SSDPCache" );
254 }
255
256 void Execute( TaskQueue *pQueue ) override // Task
257 {
259
260 int nCount = SSDPCache::Instance()->RemoveStale();
261
262 if (nCount > 0)
263 {
264 LOG(VB_UPNP, LOG_INFO,
265 QString("SSDPCacheTask - Removed %1 stale entries.")
266 .arg(nCount));
267 }
268
269 if ((m_nExecuteCount % 60) == 0)
271
272 pQueue->AddTask( m_nInterval, (Task *)this );
273 }
274
275};
276
279//
280// SSDPCache Implementation
281//
284
286{
287 return g_pSSDPCache ? g_pSSDPCache : (g_pSSDPCache = new SSDPCache());
288
289}
290
292//
294
296{
297 LOG(VB_UPNP, LOG_DEBUG, "SSDPCache - Constructor");
298
299 // ----------------------------------------------------------------------
300 // Add Task to keep SSDPCache purged of stale entries.
301 // ----------------------------------------------------------------------
302
303 auto *task = new SSDPCacheTask();
305 task->DecrRef();
306}
307
309//
311
313{
314 // FIXME: Using this causes crashes
315#if 0
316 LOG(VB_UPNP, LOG_DEBUG, "SSDPCache - Destructor");
317#endif
318
319 Clear();
320}
321
323//
325
327{
328 QMutexLocker locker(&m_mutex);
329
330 for (auto *const it : std::as_const(m_cache))
331 {
332 if (it)
333 it->DecrRef();
334 }
335
336 m_cache.clear();
337}
338
342{
343 QMutexLocker locker(&m_mutex);
344
345 SSDPCacheEntriesMap::iterator it = m_cache.find(sURI);
346 if (it != m_cache.end() && (*it != nullptr))
347 (*it)->IncrRef();
348
349 return (it != m_cache.end()) ? *it : nullptr;
350}
351
354DeviceLocation *SSDPCache::Find(const QString &sURI, const QString &sUSN)
355{
356 DeviceLocation *pEntry = nullptr;
357 SSDPCacheEntries *pEntries = Find(sURI);
358
359 if (pEntries != nullptr)
360 {
361 pEntry = pEntries->Find(sUSN);
362 pEntries->DecrRef();
363 }
364
365 return pEntry;
366}
367
369//
371
372void SSDPCache::Add( const QString &sURI,
373 const QString &sUSN,
374 const QString &sLocation,
375 std::chrono::seconds sExpiresInSecs )
376{
377 // --------------------------------------------------------------
378 // Calculate when this cache entry should expire.
379 // --------------------------------------------------------------
380
381 auto ttExpires = nowAsDuration<std::chrono::microseconds>() + sExpiresInSecs;
382
383 // --------------------------------------------------------------
384 // Get a Pointer to a Entries QDict... (Create if not found)
385 // --------------------------------------------------------------
386
387 SSDPCacheEntries *pEntries = nullptr;
388 {
389 QMutexLocker locker(&m_mutex);
390 SSDPCacheEntriesMap::iterator it = m_cache.find(sURI);
391 if (it == m_cache.end() || (*it == nullptr))
392 {
393 pEntries = new SSDPCacheEntries();
394 it = m_cache.insert(sURI, pEntries);
395 }
396 pEntries = *it;
397 pEntries->IncrRef();
398 }
399
400 // --------------------------------------------------------------
401 // See if the Entries Collection contains our USN... (Create if not found)
402 // --------------------------------------------------------------
403
404 DeviceLocation *pEntry = pEntries->Find(sUSN);
405 if (pEntry == nullptr)
406 {
407 QUrl url = sLocation;
408 QString host = url.host();
409 QString hostport = QString("%1:%2").arg(host).arg(url.port(80));
410 // Check if the port can be reached. If not we won't use it.
411 // Keep a cache of good and bad URLs found so as not to
412 // overwhelm the thread will portchecker requests.
413 // Allow up to 3 atempts before a port is finally treated as bad.
414 if (m_badUrlList.count(hostport) < 3)
415 {
416 bool isGoodUrl = false;
417 if (m_goodUrlList.contains(hostport))
418 isGoodUrl = true;
419 else
420 {
421 PortChecker checker;
422 if (checker.checkPort(host, url.port(80), 5s))
423 {
424 m_goodUrlList.append(hostport);
425 isGoodUrl=true;
426 }
427 else
428 {
429 m_badUrlList.append(hostport);
430 }
431 }
432 // Only add if the device can be connected
433 if (isGoodUrl)
434 {
435 pEntry = new DeviceLocation(sURI, sUSN, sLocation, ttExpires);
436 pEntries->Insert(sUSN, pEntry);
437 NotifyAdd(sURI, sUSN, sLocation);
438 }
439 }
440 }
441 else
442 {
443 // Only accept locations that have been tested when added.
444 if (pEntry->m_sLocation == sLocation)
445 pEntry->m_ttExpires = ttExpires;
446 }
447
448 if (pEntry)
449 pEntry->DecrRef();
450 pEntries->DecrRef();
451}
452
454//
456
457void SSDPCache::Remove( const QString &sURI, const QString &sUSN )
458{
459 Lock();
460
461 // --------------------------------------------------------------
462 // Get a Pointer to a Entries QDict... (Create if not found)
463 // --------------------------------------------------------------
464
465 SSDPCacheEntriesMap::Iterator it = m_cache.find( sURI );
466
467 if (it != m_cache.end())
468 {
469 SSDPCacheEntries *pEntries = *it;
470
471 if (pEntries != nullptr)
472 {
473 pEntries->IncrRef();
474
475 pEntries->Remove( sUSN );
476
477 if (pEntries->Count() == 0)
478 {
479 pEntries->DecrRef();
480 m_cache.erase(it);
481 }
482
483 pEntries->DecrRef();
484 }
485 }
486
487 Unlock();
488
489 // -=>TODO:
490 // Should this only by notified if we actually had any entry removed?
491
492 NotifyRemove( sURI, sUSN );
493}
494
496//
498
500{
501 int nCount = 0;
502 QStringList lstKeys;
503
504 auto ttNow = nowAsDuration<std::chrono::microseconds>();
505
506 Lock();
507
508 // ----------------------------------------------------------------------
509 // Iterate through all Type URI's and build list of stale entries keys
510 // ----------------------------------------------------------------------
511
512 for (auto it = m_cache.begin(); it != m_cache.end(); ++it )
513 {
514 SSDPCacheEntries *pEntries = *it;
515
516 if (pEntries != nullptr)
517 {
518 pEntries->IncrRef();
519
520 nCount += pEntries->RemoveStale( ttNow );
521
522 if (pEntries->Count() == 0)
523 lstKeys.append( it.key() );
524
525 pEntries->DecrRef();
526 }
527 }
528
529 nCount = lstKeys.count();
530
531 // ----------------------------------------------------------------------
532 // Iterate through list of keys and remove them.
533 // (This avoids issues when removing from a QMap while iterating it)
534 // ----------------------------------------------------------------------
535
536 for (const auto & key : std::as_const(lstKeys))
537 {
538 SSDPCacheEntriesMap::iterator it = m_cache.find( key );
539 if (it == m_cache.end())
540 continue;
541
542 if (*it)
543 {
544 (*it)->DecrRef();
545 m_cache.erase(it);
546 }
547 }
548
549 Unlock();
550
551 return nCount;
552}
553
555//
557
558void SSDPCache::NotifyAdd( const QString &sURI,
559 const QString &sUSN,
560 const QString &sLocation )
561{
562 QStringList values;
563
564 values.append( sURI );
565 values.append( sUSN );
566 values.append( sLocation );
567
568 MythEvent me( "SSDP_ADD", values );
569
570 dispatch( me );
571}
572
574//
576
577void SSDPCache::NotifyRemove( const QString &sURI, const QString &sUSN )
578{
579 QStringList values;
580
581 values.append( sURI );
582 values.append( sUSN );
583
584 MythEvent me( "SSDP_REMOVE", values );
585
586 dispatch( me );
587}
588
591 QTextStream &os, uint *pnDevCount, uint *pnEntryCount) const
592{
593 QMutexLocker locker(&m_mutex);
594
595 if (pnDevCount != nullptr)
596 *pnDevCount = 0;
597 if (pnEntryCount != nullptr)
598 *pnEntryCount = 0;
599
600 for (auto it = m_cache.cbegin(); it != m_cache.cend(); ++it)
601 {
602 if (*it != nullptr)
603 {
604 os << "<Device uri='" << it.key() << "'>" << Qt::endl;
605
606 uint tmp = 0;
607
608 (*it)->OutputXML(os, &tmp);
609
610 if (pnEntryCount != nullptr)
611 *pnEntryCount += tmp;
612
613 os << "</Device>" << Qt::endl;
614
615 if (pnDevCount != nullptr)
616 (*pnDevCount)++;
617 }
618 }
619 os << Qt::flush;
620
621 return os;
622}
623
626{
627 if (!VERBOSE_LEVEL_CHECK(VB_UPNP, LOG_DEBUG))
628 return;
629
630 QMutexLocker locker(&m_mutex);
631
632 LOG(VB_UPNP, LOG_DEBUG, "========================================"
633 "=======================================");
634 LOG(VB_UPNP, LOG_DEBUG, QString(" URI (type) - Found: %1 Entries - "
635 "%2 have been Allocated. ")
636 .arg(m_cache.count()).arg(SSDPCacheEntries::g_nAllocated));
637 LOG(VB_UPNP, LOG_DEBUG, " \t\tUSN (unique id)\t\t | Expires"
638 "\t | Location");
639 LOG(VB_UPNP, LOG_DEBUG, "----------------------------------------"
640 "---------------------------------------");
641
642 uint nCount = 0;
643 for (auto it = m_cache.cbegin(); it != m_cache.cend(); ++it)
644 {
645 if (*it != nullptr)
646 {
647 LOG(VB_UPNP, LOG_DEBUG, it.key());
648 (*it)->Dump(nCount);
649 LOG(VB_UPNP, LOG_DEBUG, " ");
650 }
651 }
652
653 LOG(VB_UPNP, LOG_DEBUG, "----------------------------------------"
654 "---------------------------------------");
655 LOG(VB_UPNP, LOG_DEBUG,
656 QString(" Found: %1 Entries - %2 have been Allocated. ")
657 .arg(nCount) .arg(DeviceLocation::g_nAllocated));
658 LOG(VB_UPNP, LOG_DEBUG, "========================================"
659 "=======================================" );
660}
QString m_sLocation
Definition: upnpdevice.h:239
static int g_nAllocated
Definition: upnpdevice.h:217
QString m_sUSN
Definition: upnpdevice.h:238
std::chrono::microseconds m_ttExpires
Definition: upnpdevice.h:240
This class is used as a container for messages.
Definition: mythevent.h:17
void dispatch(const MythEvent &event)
Dispatch an event to all listeners.
Small class to handle TCP port checking and finding link-local context.
Definition: portchecker.h:45
bool checkPort(QString &host, int port, std::chrono::milliseconds timeLimit=30s, bool linkLocalOnly=false)
Check if a port is open and sort out the link-local scope.
Definition: portchecker.cpp:74
General purpose reference counter.
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
virtual int IncrRef(void)
Increments reference count.
static QString GetNormalizedUSN(const QString &sUSN)
Returns a normalized USN, so that capitalization of the uuid is not an issue.
Definition: ssdpcache.cpp:223
void Insert(const QString &sUSN, DeviceLocation *pEntry)
Inserts a device location into the cache.
Definition: ssdpcache.cpp:99
DeviceLocation * Find(const QString &sUSN)
Finds the Device in the cache, returns nullptr when absent.
Definition: ssdpcache.cpp:61
uint Count(void) const
Definition: ssdpcache.h:50
uint RemoveStale(std::chrono::microseconds ttNow)
Removes expired cache entries, returning the number removed.
Definition: ssdpcache.cpp:145
void GetEntryMap(EntryMap &map)
Returns a copy of the EntryMap.
Definition: ssdpcache.cpp:87
void Dump(uint &nEntryCount) const
Prints this service to the console in human readable form.
Definition: ssdpcache.cpp:202
void Clear(void)
Clears the cache of all entries.
Definition: ssdpcache.cpp:46
void Remove(const QString &sUSN)
Removes a specific entry from the cache.
Definition: ssdpcache.cpp:122
EntryMap m_mapEntries
Definition: ssdpcache.h:72
~SSDPCacheEntries() override
Destructor protected to enforce Release method usage.
Definition: ssdpcache.cpp:39
static int g_nAllocated
Definition: ssdpcache.h:68
QTextStream & OutputXML(QTextStream &os, uint *pnEntryCount=nullptr) const
Outputs the XML for this service.
Definition: ssdpcache.cpp:178
QMutex m_mutex
Definition: ssdpcache.h:71
DeviceLocation * GetFirst(void)
Returns random entry in cache, returns nullptr when list is empty.
Definition: ssdpcache.cpp:75
~SSDPCacheTask() override=default
QString Name() override
Definition: ssdpcache.cpp:251
std::chrono::milliseconds m_nInterval
Definition: ssdpcache.cpp:235
void Execute(TaskQueue *pQueue) override
Definition: ssdpcache.cpp:256
void Lock()
Definition: ssdpcache.h:122
SSDPCacheEntriesMap m_cache
Definition: ssdpcache.h:100
void NotifyAdd(const QString &sURI, const QString &sUSN, const QString &sLocation)
Definition: ssdpcache.cpp:558
void Clear()
Definition: ssdpcache.cpp:326
QTextStream & OutputXML(QTextStream &os, uint *pnDevCount=nullptr, uint *pnEntryCount=nullptr) const
Outputs the XML for this device.
Definition: ssdpcache.cpp:590
QMutex m_mutex
Definition: ssdpcache.h:99
static SSDPCache * g_pSSDPCache
Definition: ssdpcache.h:93
~SSDPCache() override
Definition: ssdpcache.cpp:312
static SSDPCache * Instance()
Definition: ssdpcache.cpp:285
void NotifyRemove(const QString &sURI, const QString &sUSN)
Definition: ssdpcache.cpp:577
void Add(const QString &sURI, const QString &sUSN, const QString &sLocation, std::chrono::seconds sExpiresInSecs)
Definition: ssdpcache.cpp:372
QStringList m_badUrlList
Definition: ssdpcache.h:94
void Unlock()
Definition: ssdpcache.h:123
QStringList m_goodUrlList
Definition: ssdpcache.h:95
int RemoveStale()
Definition: ssdpcache.cpp:499
SSDPCacheEntries * Find(const QString &sURI)
Finds the SSDPCacheEntries in the cache, returns nullptr when absent.
Definition: ssdpcache.cpp:341
void Remove(const QString &sURI, const QString &sUSN)
Definition: ssdpcache.cpp:457
void Dump(void)
Prints this device to the console in a human readable form.
Definition: ssdpcache.cpp:625
void AddTask(std::chrono::milliseconds msec, Task *pTask)
Add a task to run in the future.
Definition: taskqueue.cpp:168
static TaskQueue * Instance()
Definition: taskqueue.cpp:55
Definition: taskqueue.h:48
unsigned int uint
Definition: freesurround.h:24
static guint32 * tmp
Definition: goom_core.cpp:26
static const QString sLocation
Definition: mythcontext.cpp:71
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QMap< QString, DeviceLocation * > EntryMap
Key == Unique Service Name (USN)
Definition: ssdpcache.h:34