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 {
419 isGoodUrl = true;
420 }
421 else
422 {
423 PortChecker checker;
424 if (checker.checkPort(host, url.port(80), 5s))
425 {
426 m_goodUrlList.append(hostport);
427 isGoodUrl=true;
428 }
429 else
430 {
431 m_badUrlList.append(hostport);
432 }
433 }
434 // Only add if the device can be connected
435 if (isGoodUrl)
436 {
437 pEntry = new DeviceLocation(sURI, sUSN, sLocation, ttExpires);
438 pEntries->Insert(sUSN, pEntry);
439 NotifyAdd(sURI, sUSN, sLocation);
440 }
441 }
442 }
443 else
444 {
445 // Only accept locations that have been tested when added.
446 if (pEntry->m_sLocation == sLocation)
447 pEntry->m_ttExpires = ttExpires;
448 }
449
450 if (pEntry)
451 pEntry->DecrRef();
452 pEntries->DecrRef();
453}
454
456//
458
459void SSDPCache::Remove( const QString &sURI, const QString &sUSN )
460{
461 Lock();
462
463 // --------------------------------------------------------------
464 // Get a Pointer to a Entries QDict... (Create if not found)
465 // --------------------------------------------------------------
466
467 SSDPCacheEntriesMap::Iterator it = m_cache.find( sURI );
468
469 if (it != m_cache.end())
470 {
471 SSDPCacheEntries *pEntries = *it;
472
473 if (pEntries != nullptr)
474 {
475 pEntries->IncrRef();
476
477 pEntries->Remove( sUSN );
478
479 if (pEntries->Count() == 0)
480 {
481 pEntries->DecrRef();
482 m_cache.erase(it);
483 }
484
485 pEntries->DecrRef();
486 }
487 }
488
489 Unlock();
490
491 // -=>TODO:
492 // Should this only by notified if we actually had any entry removed?
493
494 NotifyRemove( sURI, sUSN );
495}
496
498//
500
502{
503 int nCount = 0;
504 QStringList lstKeys;
505
506 auto ttNow = nowAsDuration<std::chrono::microseconds>();
507
508 Lock();
509
510 // ----------------------------------------------------------------------
511 // Iterate through all Type URI's and build list of stale entries keys
512 // ----------------------------------------------------------------------
513
514 for (auto it = m_cache.begin(); it != m_cache.end(); ++it )
515 {
516 SSDPCacheEntries *pEntries = *it;
517
518 if (pEntries != nullptr)
519 {
520 pEntries->IncrRef();
521
522 nCount += pEntries->RemoveStale( ttNow );
523
524 if (pEntries->Count() == 0)
525 lstKeys.append( it.key() );
526
527 pEntries->DecrRef();
528 }
529 }
530
531 nCount = lstKeys.count();
532
533 // ----------------------------------------------------------------------
534 // Iterate through list of keys and remove them.
535 // (This avoids issues when removing from a QMap while iterating it)
536 // ----------------------------------------------------------------------
537
538 for (const auto & key : std::as_const(lstKeys))
539 {
540 SSDPCacheEntriesMap::iterator it = m_cache.find( key );
541 if (it == m_cache.end())
542 continue;
543
544 if (*it)
545 {
546 (*it)->DecrRef();
547 m_cache.erase(it);
548 }
549 }
550
551 Unlock();
552
553 return nCount;
554}
555
557//
559
560void SSDPCache::NotifyAdd( const QString &sURI,
561 const QString &sUSN,
562 const QString &sLocation )
563{
564 QStringList values;
565
566 values.append( sURI );
567 values.append( sUSN );
568 values.append( sLocation );
569
570 MythEvent me( "SSDP_ADD", values );
571
572 dispatch( me );
573}
574
576//
578
579void SSDPCache::NotifyRemove( const QString &sURI, const QString &sUSN )
580{
581 QStringList values;
582
583 values.append( sURI );
584 values.append( sUSN );
585
586 MythEvent me( "SSDP_REMOVE", values );
587
588 dispatch( me );
589}
590
593 QTextStream &os, uint *pnDevCount, uint *pnEntryCount) const
594{
595 QMutexLocker locker(&m_mutex);
596
597 if (pnDevCount != nullptr)
598 *pnDevCount = 0;
599 if (pnEntryCount != nullptr)
600 *pnEntryCount = 0;
601
602 for (auto it = m_cache.cbegin(); it != m_cache.cend(); ++it)
603 {
604 if (*it != nullptr)
605 {
606 os << "<Device uri='" << it.key() << "'>" << Qt::endl;
607
608 uint tmp = 0;
609
610 (*it)->OutputXML(os, &tmp);
611
612 if (pnEntryCount != nullptr)
613 *pnEntryCount += tmp;
614
615 os << "</Device>" << Qt::endl;
616
617 if (pnDevCount != nullptr)
618 (*pnDevCount)++;
619 }
620 }
621 os << Qt::flush;
622
623 return os;
624}
625
628{
629 if (!VERBOSE_LEVEL_CHECK(VB_UPNP, LOG_DEBUG))
630 return;
631
632 QMutexLocker locker(&m_mutex);
633
634 LOG(VB_UPNP, LOG_DEBUG, "========================================"
635 "=======================================");
636 LOG(VB_UPNP, LOG_DEBUG, QString(" URI (type) - Found: %1 Entries - "
637 "%2 have been Allocated. ")
638 .arg(m_cache.count()).arg(SSDPCacheEntries::g_nAllocated));
639 LOG(VB_UPNP, LOG_DEBUG, " \t\tUSN (unique id)\t\t | Expires"
640 "\t | Location");
641 LOG(VB_UPNP, LOG_DEBUG, "----------------------------------------"
642 "---------------------------------------");
643
644 uint nCount = 0;
645 for (auto it = m_cache.cbegin(); it != m_cache.cend(); ++it)
646 {
647 if (*it != nullptr)
648 {
649 LOG(VB_UPNP, LOG_DEBUG, it.key());
650 (*it)->Dump(nCount);
651 LOG(VB_UPNP, LOG_DEBUG, " ");
652 }
653 }
654
655 LOG(VB_UPNP, LOG_DEBUG, "----------------------------------------"
656 "---------------------------------------");
657 LOG(VB_UPNP, LOG_DEBUG,
658 QString(" Found: %1 Entries - %2 have been Allocated. ")
659 .arg(nCount) .arg(DeviceLocation::g_nAllocated));
660 LOG(VB_UPNP, LOG_DEBUG, "========================================"
661 "=======================================" );
662}
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(const QString &host, int port, std::chrono::milliseconds timeLimit=30s)
Check if a port is open.
Definition: portchecker.cpp:63
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:560
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:592
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:579
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:501
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:459
void Dump(void)
Prints this device to the console in a human readable form.
Definition: ssdpcache.cpp:627
void AddTask(std::chrono::milliseconds msec, Task *pTask)
Add a task to run in the future.
Definition: taskqueue.cpp:170
static TaskQueue * Instance()
Definition: taskqueue.cpp:57
Definition: taskqueue.h:48
unsigned int uint
Definition: compat.h:60
static const QString sLocation
Definition: mythcontext.cpp:76
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