MythTV  master
upnpscanner.cpp
Go to the documentation of this file.
1 // C++
2 #include <chrono> // for milliseconds
3 #include <thread> // for sleep_for
4 #include <utility>
5 
6 // Qt
7 #include <QCoreApplication>
8 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
9 #include <QStringConverter>
10 #else
11 #include <QTextCodec>
12 #endif
13 
14 // MythTV
17 #include "libmythupnp/ssdp.h"
18 
19 // MythFrontend
20 #include "upnpscanner.h"
21 
22 #define LOC QString("UPnPScan: ")
23 #define ERR QString("UPnPScan error: ")
24 
25 static constexpr uint8_t MAX_ATTEMPTS { 5 };
26 
28 {
29  // items don't need scanning
30  if (!m_url.isEmpty())
31  return {};
32 
33  // scan this container
34  if (!m_scanned)
35  return m_id;
36 
37  // scan children
38  QMutableMapIterator<QString,MediaServerItem> it(m_children);
39  while (it.hasNext())
40  {
41  it.next();
42  QString result = it.value().NextUnbrowsed();
43  if (!result.isEmpty())
44  return result;
45  }
46 
47  return {};
48 }
49 
51 {
52  if (m_id == id)
53  return this;
54 
55  QMutableMapIterator<QString,MediaServerItem> it(m_children);
56  while (it.hasNext())
57  {
58  it.next();
59  MediaServerItem* result = it.value().Find(id);
60  if (result)
61  return result;
62  }
63 
64  return nullptr;
65 }
66 
68 {
69  if (m_id == item.m_parentid)
70  {
71  m_children.insert(item.m_id, item);
72  return true;
73  }
74  return false;
75 }
76 
78 {
79  m_children.clear();
80  m_scanned = false;
81 }
82 
88 {
89  public:
91  : MediaServerItem(QString("0"), QString(), QString(), QString()),
92  m_friendlyName(QString("Unknown"))
93  {
94  }
95  explicit UpnpMediaServer(QUrl URL)
96  : MediaServerItem(QString("0"), QString(), QString(), QString()),
97  m_serverURL(std::move(URL)),
98  m_friendlyName(QString("Unknown"))
99  {
100  }
101 
102  bool ResetContent(int new_id)
103  {
104  bool result = true;
105  if (m_systemUpdateID != -1)
106  {
107  result = false;
108  Reset();
109  }
110  m_systemUpdateID = new_id;
111  return result;
112  }
113 
117  QString m_eventSubPath;
118  QString m_friendlyName;
119  uint8_t m_connectionAttempts {0};
120  bool m_subscribed {false};
123 };
124 
128 QRecursiveMutex* UPNPScanner::gUPNPScannerLock = new QRecursiveMutex();
129 
141 {
142  Stop();
143 }
144 
149 void UPNPScanner::Enable(bool enable, UPNPSubscription *sub)
150 {
151  QMutexLocker locker(gUPNPScannerLock);
152  gUPNPScannerEnabled = enable;
153  Instance(sub);
154 }
155 
162 {
163  QMutexLocker locker(gUPNPScannerLock);
164  if (!gUPNPScannerEnabled)
165  {
166  if (gUPNPScannerThread)
167  {
170  }
171  delete gUPNPScannerThread;
172  gUPNPScannerThread = nullptr;
173  delete gUPNPScanner;
174  gUPNPScanner = nullptr;
175  return nullptr;
176  }
177 
178  if (!gUPNPScannerThread)
179  gUPNPScannerThread = new MThread("UPnPScanner");
180  if (!gUPNPScanner)
181  gUPNPScanner = new UPNPScanner(sub);
182 
184  {
185  gUPNPScanner->moveToThread(gUPNPScannerThread->qthread());
186  QObject::connect(
187  gUPNPScannerThread->qthread(), &QThread::started,
189  gUPNPScannerThread->start(QThread::LowestPriority);
190  }
191 
192  return gUPNPScanner;
193 }
200 {
201  m_fullscan = true;
202  auto *me = new MythEvent(QString("UPNP_STARTSCAN"));
203  qApp->postEvent(this, me);
204 }
205 
213  meta_dir_node *node)
214 {
215  // nothing to see..
216  QMap<QString,QString> servers = ServerList();
217  if (servers.isEmpty())
218  return;
219 
220  // Add MediaServers
221  LOG(VB_GENERAL, LOG_INFO, QString("Adding MediaServer metadata."));
222 
223  smart_dir_node mediaservers = node->addSubDir(tr("Media Servers"));
224  mediaservers->setPathRoot();
225 
226  m_lock.lock();
227  QMutableHashIterator<QString,UpnpMediaServer*> it(m_servers);
228  while (it.hasNext())
229  {
230  it.next();
231  if (!it.value()->m_subscribed)
232  continue;
233 
234  QString usn = it.key();
235  GetServerContent(usn, it.value(), list, mediaservers.get());
236  }
237  m_lock.unlock();
238 }
239 
245  meta_dir_node *node)
246 {
247  // nothing to see..
248  QMap<QString,QString> servers = ServerList();
249  if (servers.isEmpty())
250  return;
251 
252  // Start scanning if it isn't already running
253  StartFullScan();
254 
255  // wait for the scanner to complete - with a 30 second timeout
256  LOG(VB_GENERAL, LOG_INFO, LOC + "Waiting for scan to complete.");
257 
258  int count = 0;
259  while (!m_scanComplete && (count++ < 300))
260  std::this_thread::sleep_for(100ms);
261 
262  // some scans may just take too long (PlayOn)
263  if (!m_scanComplete)
264  LOG(VB_GENERAL, LOG_ERR, LOC + "MediaServer scan is incomplete.");
265  else
266  LOG(VB_GENERAL, LOG_INFO, LOC + "MediaServer scanning finished.");
267 
268 
269  smart_dir_node mediaservers = node->addSubDir(tr("Media Servers"));
270  mediaservers->setPathRoot();
271 
272  m_lock.lock();
273  QMutableHashIterator<QString,UpnpMediaServer*> it(m_servers);
274  while (it.hasNext())
275  {
276  it.next();
277  if (!it.value()->m_subscribed)
278  continue;
279 
280  QString usn = it.key();
281  GetServerContent(usn, it.value(), list, mediaservers.get());
282  }
283  m_lock.unlock();
284 }
285 
286 bool UPNPScanner::GetMetadata(QVariant &data)
287 {
288  // we need a USN and objectID
289  if (!data.canConvert<QStringList>())
290  return false;
291 
292  QStringList list = data.toStringList();
293  if (list.size() != 2)
294  return false;
295 
296  const QString& usn = list[0];
297  QString object = list[1];
298 
299  m_lock.lock();
300  bool valid = m_servers.contains(usn);
301  if (valid)
302  {
303  MediaServerItem* item = m_servers[usn]->Find(object);
304  valid = item ? !item->m_scanned : false;
305  }
306  m_lock.unlock();
307  if (!valid)
308  return false;
309 
310  auto *me = new MythEvent("UPNP_BROWSEOBJECT", list);
311  qApp->postEvent(this, me);
312 
313  int count = 0;
314  bool found = false;
315  LOG(VB_GENERAL, LOG_INFO, "START");
316  while (!found && (count++ < 100)) // 10 seconds
317  {
318  std::this_thread::sleep_for(100ms);
319  m_lock.lock();
320  if (m_servers.contains(usn))
321  {
322  MediaServerItem *item = m_servers[usn]->Find(object);
323  if (item)
324  {
325  found = item->m_scanned;
326  }
327  else
328  {
329  LOG(VB_GENERAL, LOG_INFO, QString("Item went away..."));
330  found = true;
331  }
332  }
333  else
334  {
335  LOG(VB_GENERAL, LOG_INFO,
336  QString("Server went away while browsing."));
337  found = true;
338  }
339  m_lock.unlock();
340  }
341  LOG(VB_GENERAL, LOG_INFO, "END");
342  return true;
343 }
344 
353  meta_dir_node *node)
354 {
355  if (!content->m_scanned)
356  {
357  smart_dir_node subnode = node->addSubDir(content->m_name);
358 
359  QStringList data;
360  data << usn;
361  data << content->m_id;
362  subnode->SetData(data);
363 
365  item->SetTitle(QString("Dummy"));
366  list->push_back(item);
367  subnode->addEntry(smart_meta_node(new meta_data_node(item.get())));
368  return;
369  }
370 
371  node->SetData(QVariant());
372 
373  if (content->m_url.isEmpty())
374  {
375  smart_dir_node container = node->addSubDir(content->m_name);
376  QMutableMapIterator<QString,MediaServerItem> it(content->m_children);
377  while (it.hasNext())
378  {
379  it.next();
380  GetServerContent(usn, &it.value(), list, container.get());
381  }
382  return;
383  }
384 
386  item->SetTitle(content->m_name);
387  list->push_back(item);
388  node->addEntry(smart_meta_node(new meta_data_node(item.get())));
389 }
390 
396 QMap<QString,QString> UPNPScanner::ServerList(void)
397 {
398  QMap<QString,QString> servers;
399  m_lock.lock();
400  QHashIterator<QString,UpnpMediaServer*> it(m_servers);
401  while (it.hasNext())
402  {
403  it.next();
404  servers.insert(it.key(), it.value()->m_friendlyName);
405  }
406  m_lock.unlock();
407  return servers;
408 }
409 
416 {
417  m_lock.lock();
418 
419  // create our network handler
420  m_network = new QNetworkAccessManager();
421  connect(m_network, &QNetworkAccessManager::finished,
423 
424  // listen for SSDP updates
425  SSDP::AddListener(this);
426 
427  // listen for subscriptions and events
428  if (m_subscription)
430 
431  // create our update timer (driven by AddServer and ParseDescription)
432  m_updateTimer = new QTimer(this);
433  m_updateTimer->setSingleShot(true);
435 
436  // create our watchdog timer (checks for stale servers)
437  m_watchdogTimer = new QTimer(this);
439  m_watchdogTimer->start(10s);
440 
441  // avoid connecting to the master backend
444 
445  m_lock.unlock();
446  LOG(VB_GENERAL, LOG_INFO, LOC + "Started");
447 }
448 
454 {
455  m_lock.lock();
456 
457  // stop listening
458  SSDP::RemoveListener(this);
459  if (m_subscription)
461 
462  // disable updates
463  if (m_updateTimer)
464  m_updateTimer->stop();
465  if (m_watchdogTimer)
466  m_watchdogTimer->stop();
467 
468  // cleanup our servers and subscriptions
469  QHashIterator<QString,UpnpMediaServer*> it(m_servers);
470  while (it.hasNext())
471  {
472  it.next();
473  if (m_subscription && it.value()->m_subscribed)
474  m_subscription->Unsubscribe(it.key());
475  if (it.value()->m_renewalTimerId)
476  killTimer(it.value()->m_renewalTimerId);
477  delete it.value();
478  }
479  m_servers.clear();
480 
481  // cleanup the network
482  for (QNetworkReply *reply : std::as_const(m_descriptionRequests))
483  {
484  reply->abort();
485  delete reply;
486  }
487  m_descriptionRequests.clear();
488  for (QNetworkReply *reply : std::as_const(m_browseRequests))
489  {
490  reply->abort();
491  delete reply;
492  }
493  m_browseRequests.clear();
494  delete m_network;
495  m_network = nullptr;
496 
497  // delete the timers
498  delete m_updateTimer;
499  delete m_watchdogTimer;
500  m_updateTimer = nullptr;
501  m_watchdogTimer = nullptr;
502 
503  m_lock.unlock();
504  LOG(VB_GENERAL, LOG_INFO, LOC + "Finished");
505 }
506 
513 {
514  // decide which servers still need to be checked
515  m_lock.lock();
516  if (m_servers.isEmpty())
517  {
518  m_lock.unlock();
519  return;
520  }
521 
522  // if our network queue is full, then we may need to come back later
523  bool reschedule = false;
524 
525  QHashIterator<QString,UpnpMediaServer*> it(m_servers);
526  while (it.hasNext())
527  {
528  it.next();
529  if ((it.value()->m_connectionAttempts < MAX_ATTEMPTS) &&
530  (it.value()->m_controlURL.isEmpty()))
531  {
532  bool sent = false;
533  QUrl url = it.value()->m_url;
534  if (!m_descriptionRequests.contains(url) &&
535  (m_descriptionRequests.empty()) &&
536  url.isValid())
537  {
538  QNetworkReply *reply = m_network->get(QNetworkRequest(url));
539  if (reply)
540  {
541  sent = true;
542  m_descriptionRequests.insert(url, reply);
543  it.value()->m_connectionAttempts++;
544  }
545  }
546  if (!sent)
547  reschedule = true;
548  }
549  }
550 
551  if (reschedule)
552  ScheduleUpdate();
553  m_lock.unlock();
554 }
555 
561 {
562  // FIXME
563  // Remove stale servers - the SSDP cache code does not send out removal
564  // notifications for expired (rather than explicitly closed) connections
565  m_lock.lock();
566  QMutableHashIterator<QString,UpnpMediaServer*> it(m_servers);
567  while (it.hasNext())
568  {
569  it.next();
570  // FIXME UPNP version comparision done wrong, we are using urn:schemas-upnp-org:device:MediaServer:4 ourselves
571  if (!SSDP::Find("urn:schemas-upnp-org:device:MediaServer:1", it.key()))
572  {
573  LOG(VB_UPNP, LOG_INFO, LOC + QString("%1 no longer in SSDP cache. Removing")
574  .arg(it.value()->m_serverURL.toString()));
575  UpnpMediaServer* last = it.value();
576  it.remove();
577  delete last;
578  }
579  }
580  m_lock.unlock();
581 }
582 
588 void UPNPScanner::replyFinished(QNetworkReply *reply)
589 {
590  if (!reply)
591  return;
592 
593  QUrl url = reply->url();
594  bool valid = reply->error() == QNetworkReply::NoError;
595 
596  if (!valid)
597  {
598  LOG(VB_UPNP, LOG_ERR, LOC +
599  QString("Network request for '%1' returned error '%2'")
600  .arg(url.toString(), reply->errorString()));
601  }
602 
603  bool description = false;
604  bool browse = false;
605 
606  m_lock.lock();
607  if (m_descriptionRequests.contains(url, reply))
608  {
609  m_descriptionRequests.remove(url, reply);
610  description = true;
611  }
612  else if (m_browseRequests.contains(url, reply))
613  {
614  m_browseRequests.remove(url, reply);
615  browse = true;
616  }
617  m_lock.unlock();
618 
619  if (browse && valid)
620  {
621  ParseBrowse(url, reply);
622  if (m_fullscan)
624  }
625  else if (description)
626  {
627  if (!valid || !ParseDescription(url, reply))
628  {
629  // if there will be no more attempts, update the logs
630  CheckFailure(url);
631  // try again
632  ScheduleUpdate();
633  }
634  }
635  else
636  {
637  LOG(VB_UPNP, LOG_ERR, LOC + "Received unknown reply");
638  }
639 
640  reply->deleteLater();
641 }
642 
646 void UPNPScanner::customEvent(QEvent *event)
647 {
648  if (event->type() != MythEvent::kMythEventMessage)
649  return;
650 
651  // UPnP events
652  auto *me = dynamic_cast<MythEvent *>(event);
653  if (me == nullptr)
654  return;
655 
656  const QString& ev = me->Message();
657 
658  if (ev == "UPNP_STARTSCAN")
659  {
661  return;
662  }
663  if (ev == "UPNP_BROWSEOBJECT")
664  {
665  if (me->ExtraDataCount() == 2)
666  {
667  QUrl url;
668  const QString& usn = me->ExtraData(0);
669  const QString& objectid = me->ExtraData(1);
670  m_lock.lock();
671  if (m_servers.contains(usn))
672  {
673  url = m_servers[usn]->m_controlURL;
674  LOG(VB_GENERAL, LOG_INFO, QString("UPNP_BROWSEOBJECT: %1->%2")
675  .arg(m_servers[usn]->m_friendlyName, objectid));
676  }
677  m_lock.unlock();
678  if (!url.isEmpty())
679  SendBrowseRequest(url, objectid);
680  }
681  return;
682  }
683  if (ev == "UPNP_EVENT")
684  {
685  auto *info = (MythInfoMapEvent*)event;
686  if (!info)
687  return;
688  if (!info->GetInfoMap())
689  return;
690 
691  QString usn = info->GetInfoMap()->value("usn");
692  QString id = info->GetInfoMap()->value("SystemUpdateID");
693  if (usn.isEmpty() || id.isEmpty())
694  return;
695 
696  m_lock.lock();
697  if (m_servers.contains(usn))
698  {
699  int newid = id.toInt();
700  if (m_servers[usn]->m_systemUpdateID != newid)
701  {
702  m_scanComplete &= m_servers[usn]->ResetContent(newid);
703  LOG(VB_GENERAL, LOG_INFO, LOC +
704  QString("New SystemUpdateID '%1' for %2").arg(id, usn));
705  Debug();
706  }
707  }
708  m_lock.unlock();
709  return;
710  }
711 
712  // process SSDP cache updates
713  QString uri = me->ExtraDataCount() > 0 ? me->ExtraData(0) : QString();
714  QString usn = me->ExtraDataCount() > 1 ? me->ExtraData(1) : QString();
715 
716  // FIXME UPNP version comparision done wrong, we are using urn:schemas-upnp-org:device:MediaServer:4 ourselves
717  if (uri == "urn:schemas-upnp-org:device:MediaServer:1")
718  {
719  QString url = (ev == "SSDP_ADD") ? me->ExtraData(2) : QString();
720  AddServer(usn, url);
721  }
722 }
723 
728 void UPNPScanner::timerEvent(QTimerEvent * event)
729 {
730  int id = event->timerId();
731  if (id)
732  killTimer(id);
733 
734  std::chrono::seconds timeout = 0s;
735  QString usn;
736 
737  m_lock.lock();
738  QHashIterator<QString,UpnpMediaServer*> it(m_servers);
739  while (it.hasNext())
740  {
741  it.next();
742  if (it.value()->m_renewalTimerId == id)
743  {
744  it.value()->m_renewalTimerId = 0;
745  usn = it.key();
746  if (m_subscription)
747  timeout = m_subscription->Renew(usn);
748  }
749  }
750  m_lock.unlock();
751 
752  if (timeout > 0s)
753  {
754  ScheduleRenewal(usn, timeout);
755  LOG(VB_GENERAL, LOG_INFO, LOC +
756  QString("Re-subscribed for %1 seconds to %2")
757  .arg(timeout.count()).arg(usn));
758  }
759 }
760 
765 {
766  m_lock.lock();
767  if (m_updateTimer && !m_updateTimer->isActive())
768  m_updateTimer->start(200ms);
769  m_lock.unlock();
770 }
771 
776 void UPNPScanner::CheckFailure(const QUrl &url)
777 {
778  m_lock.lock();
779  QHashIterator<QString,UpnpMediaServer*> it(m_servers);
780  while (it.hasNext())
781  {
782  it.next();
783  if (it.value()->m_serverURL == url && it.value()->m_connectionAttempts == MAX_ATTEMPTS)
784  {
785  Debug();
786  break;
787  }
788  }
789  m_lock.unlock();
790 }
791 
796 {
797  m_lock.lock();
798  LOG(VB_UPNP, LOG_INFO, LOC + QString("%1 media servers discovered:")
799  .arg(m_servers.size()));
800  QHashIterator<QString,UpnpMediaServer*> it(m_servers);
801  while (it.hasNext())
802  {
803  it.next();
804  QString status = "Probing";
805  if (it.value()->m_controlURL.toString().isEmpty())
806  {
807  if (it.value()->m_connectionAttempts >= MAX_ATTEMPTS)
808  status = "Failed";
809  }
810  else
811  {
812  status = "Yes";
813  }
814  LOG(VB_UPNP, LOG_INFO, LOC +
815  QString("'%1' Connected: %2 Subscribed: %3 SystemUpdateID: "
816  "%4 timerId: %5")
817  .arg(it.value()->m_friendlyName, status,
818  it.value()->m_subscribed ? "Yes" : "No",
819  QString::number(it.value()->m_systemUpdateID),
820  QString::number(it.value()->m_renewalTimerId)));
821  }
822  m_lock.unlock();
823 }
824 
834 {
835  QMutexLocker locker(&m_lock);
836 
837  QHashIterator<QString,UpnpMediaServer*> it(m_servers);
838  bool complete = true;
839  while (it.hasNext())
840  {
841  it.next();
842  if (it.value()->m_subscribed)
843  {
844  // limit browse requests to one active per server
845  if (m_browseRequests.contains(it.value()->m_controlURL))
846  {
847  complete = false;
848  continue;
849  }
850 
851  QString next = it.value()->NextUnbrowsed();
852  if (!next.isEmpty())
853  {
854  complete = false;
855  SendBrowseRequest(it.value()->m_controlURL, next);
856  continue;
857  }
858 
859  LOG(VB_UPNP, LOG_INFO, LOC + QString("Scan completed for %1")
860  .arg(it.value()->m_friendlyName));
861  }
862  }
863 
864  if (complete)
865  {
866  LOG(VB_GENERAL, LOG_INFO, LOC +
867  QString("Media Server scan is complete."));
868  m_scanComplete = true;
869  m_fullscan = false;
870  }
871 }
872 
878 void UPNPScanner::SendBrowseRequest(const QUrl &url, const QString &objectid)
879 {
880  QNetworkRequest req = QNetworkRequest(url);
881  req.setRawHeader("CONTENT-TYPE", "text/xml; charset=\"utf-8\"");
882  req.setRawHeader("SOAPACTION",
883  "\"urn:schemas-upnp-org:service:ContentDirectory:1#Browse\"");
884 #if 0
885  req.setRawHeader("MAN", "\"http://schemasxmlsoap.org/soap/envelope/\"");
886  req.setRawHeader("01-SOAPACTION",
887  "\"urn:schemas-upnp-org:service:ContentDirectory:1#Browse\"");
888 #endif
889 
890  QByteArray body;
891  QTextStream data(&body);
892 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
893  data.setCodec(QTextCodec::codecForName("UTF-8"));
894 #else
895  data.setEncoding(QStringConverter::Utf8);
896 #endif
897  data << "<?xml version=\"1.0\"?>\r\n";
898  data << "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n";
899  data << " <s:Body>\r\n";
900  data << " <u:Browse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">\r\n";
901  data << " <ObjectID>" << objectid.toUtf8() << "</ObjectID>\r\n";
902  data << " <BrowseFlag>BrowseDirectChildren</BrowseFlag>\r\n";
903  data << " <Filter>*</Filter>\r\n";
904  data << " <StartingIndex>0</StartingIndex>\r\n";
905  data << " <RequestedCount>0</RequestedCount>\r\n";
906  data << " <SortCriteria></SortCriteria>\r\n";
907  data << " </u:Browse>\r\n";
908  data << " </s:Body>\r\n";
909  data << "</s:Envelope>\r\n";
910  data.flush();
911 
912  m_lock.lock();
913  QNetworkReply *reply = m_network->post(req, body);
914  if (reply)
915  m_browseRequests.insert(url, reply);
916  m_lock.unlock();
917 }
918 
924 void UPNPScanner::AddServer(const QString &usn, const QString &url)
925 {
926  if (url.isEmpty())
927  {
928  RemoveServer(usn);
929  return;
930  }
931 
932  // sometimes initialisation is too early and m_masterHost is empty
933  if (m_masterHost.isEmpty())
934  {
937  }
938 
939  QUrl qurl(url);
940  if (qurl.host() == m_masterHost && qurl.port() == m_masterPort)
941  {
942  LOG(VB_UPNP, LOG_INFO, LOC + "Ignoring master backend.");
943  return;
944  }
945 
946  m_lock.lock();
947  if (!m_servers.contains(usn))
948  {
949  m_servers.insert(usn, new UpnpMediaServer(url));
950  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Adding: %1").arg(usn));
951  ScheduleUpdate();
952  }
953  m_lock.unlock();
954 }
955 
959 void UPNPScanner::RemoveServer(const QString &usn)
960 {
961  m_lock.lock();
962  if (m_servers.contains(usn))
963  {
964  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Removing: %1").arg(usn));
965  UpnpMediaServer* old = m_servers[usn];
966  if (old->m_renewalTimerId)
967  killTimer(old->m_renewalTimerId);
968  m_servers.remove(usn);
969  delete old;
970  if (m_subscription)
971  m_subscription->Remove(usn);
972  }
973  m_lock.unlock();
974 
975  Debug();
976 }
977 
981 void UPNPScanner::ScheduleRenewal(const QString &usn, std::chrono::seconds timeout)
982 {
983  // sanitise the timeout
984  std::chrono::seconds twelvehours { 12h };
985  std::chrono::seconds renew = std::clamp(timeout - 10s, 10s, twelvehours);
986 
987  m_lock.lock();
988  if (m_servers.contains(usn))
989  m_servers[usn]->m_renewalTimerId = startTimer(renew);
990  m_lock.unlock();
991 }
992 
997 void UPNPScanner::ParseBrowse(const QUrl &url, QNetworkReply *reply)
998 {
999  QByteArray data = reply->readAll();
1000  if (data.isEmpty())
1001  return;
1002 
1003  // Open the response for parsing
1004  auto *parent = new QDomDocument();
1005 #if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1006  QString errorMessage;
1007  int errorLine = 0;
1008  int errorColumn = 0;
1009  if (!parent->setContent(data, false, &errorMessage, &errorLine,
1010  &errorColumn))
1011  {
1012  LOG(VB_GENERAL, LOG_ERR, LOC +
1013  QString("DIDL Parse error, Line: %1 Col: %2 Error: '%3'")
1014  .arg(errorLine).arg(errorColumn).arg(errorMessage));
1015  delete parent;
1016  return;
1017  }
1018 #else
1019  auto parseResult = parent->setContent(data);
1020  if (!parseResult)
1021  {
1022  LOG(VB_GENERAL, LOG_ERR, LOC +
1023  QString("DIDL Parse error, Line: %1 Col: %2 Error: '%3'")
1024  .arg(parseResult.errorLine).arg(parseResult.errorColumn)
1025  .arg(parseResult.errorMessage));
1026  delete parent;
1027  return;
1028  }
1029 #endif
1030 
1031  LOG(VB_UPNP, LOG_INFO, "\n\n" + parent->toString(4) + "\n\n");
1032 
1033  // pull out the actual result
1034  QDomDocument *result = nullptr;
1035  uint num = 0;
1036  uint total = 0;
1037  uint updateid = 0;
1038  QDomElement docElem = parent->documentElement();
1039  QDomNode n = docElem.firstChild();
1040  if (!n.isNull())
1041  result = FindResult(n, num, total, updateid);
1042  delete parent;
1043 
1044  if (!result || num < 1 || total < 1)
1045  {
1046  LOG(VB_GENERAL, LOG_ERR, LOC +
1047  QString("Failed to find result for %1") .arg(url.toString()));
1048  return;
1049  }
1050 
1051  // determine the 'server' which requested the browse
1052  m_lock.lock();
1053 
1054  UpnpMediaServer* server = nullptr;
1055  QHashIterator<QString,UpnpMediaServer*> it(m_servers);
1056  while (it.hasNext())
1057  {
1058  it.next();
1059  if (url == it.value()->m_controlURL)
1060  {
1061  server = it.value();
1062  break;
1063  }
1064  }
1065 
1066  // discard unmatched responses
1067  if (!server)
1068  {
1069  m_lock.unlock();
1070  LOG(VB_GENERAL, LOG_ERR, LOC +
1071  QString("Received unknown response for %1").arg(url.toString()));
1072  return;
1073  }
1074 
1075  // check the update ID
1076  if (server->m_systemUpdateID != (int)updateid)
1077  {
1078  // if this is not the root container, this browse will now fail
1079  // as the appropriate parentID will not be found
1080  LOG(VB_GENERAL, LOG_ERR, LOC +
1081  QString("%1 updateID changed during browse (old %2 new %3)")
1082  .arg(server->m_friendlyName).arg(server->m_systemUpdateID)
1083  .arg(updateid));
1084  m_scanComplete &= server->ResetContent(updateid);
1085  Debug();
1086  }
1087 
1088  // find containers (directories) and actual items and add them and reset
1089  // the parent when we have found the first item
1090  bool reset = true;
1091  docElem = result->documentElement();
1092  n = docElem.firstChild();
1093  while (!n.isNull())
1094  {
1095  FindItems(n, *server, reset);
1096  n = n.nextSibling();
1097  }
1098  delete result;
1099 
1100  m_lock.unlock();
1101 }
1102 
1104  bool &resetparent)
1105 {
1106  QDomElement node = n.toElement();
1107  if (node.isNull())
1108  return;
1109 
1110  if (node.tagName() == "container")
1111  {
1112  QString title = "ERROR";
1113  QDomNode next = node.firstChild();
1114  while (!next.isNull())
1115  {
1116  QDomElement container = next.toElement();
1117  if (!container.isNull() && container.tagName() == "title")
1118  title = container.text();
1119  next = next.nextSibling();
1120  }
1121 
1122  QString thisid = node.attribute("id", "ERROR");
1123  QString parentid = node.attribute("parentID", "ERROR");
1124  MediaServerItem container =
1125  MediaServerItem(thisid, parentid, title, QString());
1126  MediaServerItem *parent = content.Find(parentid);
1127  if (parent)
1128  {
1129  if (resetparent)
1130  {
1131  parent->Reset();
1132  resetparent = false;
1133  }
1134  parent->m_scanned = true;
1135  parent->Add(container);
1136  }
1137  return;
1138  }
1139 
1140  if (node.tagName() == "item")
1141  {
1142  QString title = "ERROR";
1143  QString url = "ERROR";
1144  QDomNode next = node.firstChild();
1145  while (!next.isNull())
1146  {
1147  QDomElement item = next.toElement();
1148  if (!item.isNull())
1149  {
1150  if(item.tagName() == "res")
1151  url = item.text();
1152  if(item.tagName() == "title")
1153  title = item.text();
1154  }
1155  next = next.nextSibling();
1156  }
1157 
1158  QString thisid = node.attribute("id", "ERROR");
1159  QString parentid = node.attribute("parentID", "ERROR");
1160  MediaServerItem item =
1161  MediaServerItem(thisid, parentid, title, url);
1162  item.m_scanned = true;
1163  MediaServerItem *parent = content.Find(parentid);
1164  if (parent)
1165  {
1166  if (resetparent)
1167  {
1168  parent->Reset();
1169  resetparent = false;
1170  }
1171  parent->m_scanned = true;
1172  parent->Add(item);
1173  }
1174  return;
1175  }
1176 
1177  QDomNode next = node.firstChild();
1178  while (!next.isNull())
1179  {
1180  FindItems(next, content, resetparent);
1181  next = next.nextSibling();
1182  }
1183 }
1184 
1185 QDomDocument* UPNPScanner::FindResult(const QDomNode &n, uint &num,
1186  uint &total, uint &updateid)
1187 {
1188  QDomDocument *result = nullptr;
1189  QDomElement node = n.toElement();
1190  if (node.isNull())
1191  return nullptr;
1192 
1193  if (node.tagName() == "NumberReturned")
1194  num = node.text().toUInt();
1195  if (node.tagName() == "TotalMatches")
1196  total = node.text().toUInt();
1197  if (node.tagName() == "UpdateID")
1198  updateid = node.text().toUInt();
1199  if (node.tagName() == "Result" && !result)
1200  {
1201  result = new QDomDocument();
1202 #if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1203  QString errorMessage;
1204  int errorLine = 0;
1205  int errorColumn = 0;
1206  if (!result->setContent(node.text(), true, &errorMessage, &errorLine, &errorColumn))
1207  {
1208  LOG(VB_GENERAL, LOG_ERR, LOC +
1209  QString("DIDL Parse error, Line: %1 Col: %2 Error: '%3'")
1210  .arg(errorLine).arg(errorColumn).arg(errorMessage));
1211  delete result;
1212  result = nullptr;
1213  }
1214 #else
1215  auto parseResult =
1216  result->setContent(node.text(),
1217  QDomDocument::ParseOption::UseNamespaceProcessing);
1218  if (!parseResult)
1219  {
1220  LOG(VB_GENERAL, LOG_ERR, LOC +
1221  QString("DIDL Parse error, Line: %1 Col: %2 Error: '%3'")
1222  .arg(parseResult.errorLine).arg(parseResult.errorColumn)
1223  .arg(parseResult.errorMessage));
1224  delete result;
1225  result = nullptr;
1226  }
1227 #endif
1228  }
1229 
1230  QDomNode next = node.firstChild();
1231  while (!next.isNull())
1232  {
1233  QDomDocument *res = FindResult(next, num, total, updateid);
1234  if (res)
1235  result = res;
1236  next = next.nextSibling();
1237  }
1238  return result;
1239 }
1240 
1245 bool UPNPScanner::ParseDescription(const QUrl &url, QNetworkReply *reply)
1246 {
1247  if (url.isEmpty() || !reply)
1248  return false;
1249 
1250  QByteArray data = reply->readAll();
1251  if (data.isEmpty())
1252  {
1253  LOG(VB_GENERAL, LOG_ERR, LOC +
1254  QString("%1 returned an empty device description.")
1255  .arg(url.toString()));
1256  return false;
1257  }
1258 
1259  // parse the device description
1260  QString controlURL = QString();
1261  QString eventURL = QString();
1262  QString friendlyName = QString("Unknown");
1263  QString URLBase = QString();
1264 
1265  QDomDocument doc;
1266 #if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1267  QString errorMessage;
1268  int errorLine = 0;
1269  int errorColumn = 0;
1270  if (!doc.setContent(data, false, &errorMessage, &errorLine, &errorColumn))
1271  {
1272  LOG(VB_GENERAL, LOG_ERR, LOC +
1273  QString("Failed to parse device description from %1")
1274  .arg(url.toString()));
1275  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Line: %1 Col: %2 Error: '%3'")
1276  .arg(errorLine).arg(errorColumn).arg(errorMessage));
1277  return false;
1278  }
1279 #else
1280  auto parseResult = doc.setContent(data);
1281  if (!parseResult)
1282  {
1283  LOG(VB_GENERAL, LOG_ERR, LOC +
1284  QString("Failed to parse device description from %1")
1285  .arg(url.toString()));
1286  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Line: %1 Col: %2 Error: '%3'")
1287  .arg(parseResult.errorLine).arg(parseResult.errorColumn)
1288  .arg(parseResult.errorMessage));
1289  return false;
1290  }
1291 #endif
1292 
1293  QDomElement docElem = doc.documentElement();
1294  QDomNode n = docElem.firstChild();
1295  while (!n.isNull())
1296  {
1297  QDomElement e1 = n.toElement();
1298  if (!e1.isNull())
1299  {
1300  if(e1.tagName() == "device")
1301  ParseDevice(e1, controlURL, eventURL, friendlyName);
1302  if (e1.tagName() == "URLBase")
1303  URLBase = e1.text();
1304  }
1305  n = n.nextSibling();
1306  }
1307 
1308  if (controlURL.isEmpty())
1309  {
1310  LOG(VB_UPNP, LOG_ERR, LOC +
1311  QString("Failed to parse device description for %1")
1312  .arg(url.toString()));
1313  return false;
1314  }
1315 
1316  // if no URLBase was provided, use the known url
1317  if (URLBase.isEmpty())
1318  URLBase = url.toString(QUrl::RemovePath | QUrl::RemoveFragment |
1319  QUrl::RemoveQuery);
1320 
1321  // strip leading slashes off the controlURL
1322  while (!controlURL.isEmpty() && controlURL.startsWith("/"))
1323  controlURL = controlURL.mid(1);
1324 
1325  // strip leading slashes off the eventURL
1326  //while (!eventURL.isEmpty() && eventURL.startsWith("/"))
1327  // eventURL = eventURL.mid(1);
1328 
1329  // strip trailing slashes off URLBase
1330  while (!URLBase.isEmpty() && URLBase.endsWith("/"))
1331  URLBase = URLBase.mid(0, URLBase.size() - 1);
1332 
1333  controlURL = URLBase + "/" + controlURL;
1334  QString fulleventURL = URLBase + "/" + eventURL;
1335 
1336  LOG(VB_UPNP, LOG_INFO, LOC + QString("Control URL for %1 at %2")
1337  .arg(friendlyName, controlURL));
1338  LOG(VB_UPNP, LOG_INFO, LOC + QString("Event URL for %1 at %2")
1339  .arg(friendlyName, fulleventURL));
1340 
1341  // update the server details. If the server has gone away since the request
1342  // was posted, this will silently fail and we won't try again
1343  QString usn;
1344  QUrl qeventurl = QUrl(fulleventURL);
1345  std::chrono::seconds timeout = 0s;
1346 
1347  m_lock.lock();
1348  QHashIterator<QString,UpnpMediaServer*> it(m_servers);
1349  while (it.hasNext())
1350  {
1351  it.next();
1352  if (it.value()->m_serverURL == url)
1353  {
1354  usn = it.key();
1355  QUrl qcontrolurl(controlURL);
1356  it.value()->m_controlURL = qcontrolurl;
1357  it.value()->m_eventSubURL = qeventurl;
1358  it.value()->m_eventSubPath = eventURL;
1359  it.value()->m_friendlyName = friendlyName;
1360  it.value()->m_name = friendlyName;
1361  break;
1362  }
1363  }
1364 
1365  if (m_subscription && !usn.isEmpty())
1366  {
1367  timeout = m_subscription->Subscribe(usn, qeventurl, eventURL);
1368  m_servers[usn]->m_subscribed = (timeout > 0s);
1369  }
1370  m_lock.unlock();
1371 
1372  if (timeout > 0s)
1373  {
1374  LOG(VB_GENERAL, LOG_INFO, LOC +
1375  QString("Subscribed for %1 seconds to %2") .arg(timeout.count()).arg(usn));
1376  ScheduleRenewal(usn, timeout);
1377  // we only scan servers we are subscribed to - and the scan is now
1378  // incomplete
1379  m_scanComplete = false;
1380  }
1381 
1382  Debug();
1383  return true;
1384 }
1385 
1386 
1387 void UPNPScanner::ParseDevice(QDomElement &element, QString &controlURL,
1388  QString &eventURL, QString &friendlyName)
1389 {
1390  QDomNode dev = element.firstChild();
1391  while (!dev.isNull())
1392  {
1393  QDomElement e = dev.toElement();
1394  if (!e.isNull())
1395  {
1396  if (e.tagName() == "friendlyName")
1397  friendlyName = e.text();
1398  if (e.tagName() == "serviceList")
1399  ParseServiceList(e, controlURL, eventURL);
1400  }
1401  dev = dev.nextSibling();
1402  }
1403 }
1404 
1405 void UPNPScanner::ParseServiceList(QDomElement &element, QString &controlURL,
1406  QString &eventURL)
1407 {
1408  QDomNode list = element.firstChild();
1409  while (!list.isNull())
1410  {
1411  QDomElement e = list.toElement();
1412  if (!e.isNull())
1413  if (e.tagName() == "service")
1414  ParseService(e, controlURL, eventURL);
1415  list = list.nextSibling();
1416  }
1417 }
1418 
1419 void UPNPScanner::ParseService(QDomElement &element, QString &controlURL,
1420  QString &eventURL)
1421 {
1422  bool iscds = false;
1423  QString control_url = QString();
1424  QString event_url = QString();
1425  QDomNode service = element.firstChild();
1426 
1427  while (!service.isNull())
1428  {
1429  QDomElement e = service.toElement();
1430  if (!e.isNull())
1431  {
1432  if (e.tagName() == "serviceType")
1433  // FIXME UPNP version comparision done wrong, we are using urn:schemas-upnp-org:device:MediaServer:4 ourselves
1434  iscds = (e.text() == "urn:schemas-upnp-org:service:ContentDirectory:1");
1435  if (e.tagName() == "controlURL")
1436  control_url = e.text();
1437  if (e.tagName() == "eventSubURL")
1438  event_url = e.text();
1439  }
1440  service = service.nextSibling();
1441  }
1442 
1443  if (iscds)
1444  {
1445  controlURL = control_url;
1446  eventURL = event_url;
1447  }
1448 }
UPNPSubscription::Renew
std::chrono::seconds Renew(const QString &usn)
Definition: upnpsubscription.cpp:128
UpnpMediaServer
Definition: upnpscanner.cpp:87
UpnpMediaServer::m_friendlyName
QString m_friendlyName
Definition: upnpscanner.cpp:118
VideoMetadata
Definition: videometadata.h:24
UPNPScanner::m_fullscan
bool m_fullscan
Definition: upnpscanner.h:129
MediaServerItem::Add
bool Add(const MediaServerItem &item)
Definition: upnpscanner.cpp:67
UPNPScanner::customEvent
void customEvent(QEvent *event) override
Processes subscription and SSDP cache update events.
Definition: upnpscanner.cpp:646
meta_dir_node::SetData
void SetData(const QVariant &data)
Definition: videometadatalistmanager.cpp:320
MThread::start
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:283
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:102
UPNPScanner::timerEvent
void timerEvent(QTimerEvent *event) override
Definition: upnpscanner.cpp:728
UPNPScanner::gUPNPScanner
static UPNPScanner * gUPNPScanner
Definition: upnpscanner.h:108
MThread::quit
void quit(void)
calls exit(0)
Definition: mthread.cpp:295
UpnpMediaServer::m_eventSubURL
QUrl m_eventSubURL
Definition: upnpscanner.cpp:116
MediaServerItem
Definition: upnpscanner.h:24
UPNPScanner::ScheduleUpdate
void ScheduleUpdate(void)
Definition: upnpscanner.cpp:764
MythEvent::kMythEventMessage
static const Type kMythEventMessage
Definition: mythevent.h:79
MediaServerItem::m_children
QMap< QString, MediaServerItem > m_children
Definition: upnpscanner.h:42
meta_node::setPathRoot
void setPathRoot(bool is_root=true)
Definition: videometadatalistmanager.cpp:226
UPNPScanner::GetInitialMetadata
void GetInitialMetadata(VideoMetadataListManager::metadata_list *list, meta_dir_node *node)
Definition: upnpscanner.cpp:212
UpnpMediaServer::m_controlURL
QUrl m_controlURL
Definition: upnpscanner.cpp:115
ssdp.h
UPNPSubscription
Definition: upnpsubscription.h:10
MThread::wait
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:300
simple_ref_ptr< meta_dir_node >
UpnpMediaServer::m_connectionAttempts
uint8_t m_connectionAttempts
Definition: upnpscanner.cpp:119
MediaServerItem::m_url
QString m_url
Definition: upnpscanner.h:40
smart_meta_node
simple_ref_ptr< meta_data_node > smart_meta_node
Definition: videometadatalistmanager.h:78
UPNPScanner::m_browseRequests
QMultiMap< QUrl, QNetworkReply * > m_browseRequests
Definition: upnpscanner.h:120
build_compdb.content
content
Definition: build_compdb.py:38
MythEvent
This class is used as a container for messages.
Definition: mythevent.h:16
UPNPScanner::UPNPScanner
UPNPScanner(UPNPSubscription *sub)
Definition: upnpscanner.h:75
UPNPScanner::m_servers
QHash< QString, UpnpMediaServer * > m_servers
Definition: upnpscanner.h:115
SSDP::Find
static SSDPCacheEntries * Find(const QString &sURI)
Definition: ssdp.h:132
meta_dir_node
Definition: videometadatalistmanager.h:83
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
UPNPScanner::m_lock
QRecursiveMutex m_lock
Definition: upnpscanner.h:114
meta_dir_node::addEntry
void addEntry(const smart_meta_node &entry)
Definition: videometadatalistmanager.cpp:375
UpnpMediaServer::m_systemUpdateID
int m_systemUpdateID
Definition: upnpscanner.cpp:122
MediaServerItem::m_id
QString m_id
Definition: upnpscanner.h:37
MythEvent::Message
const QString & Message() const
Definition: mythevent.h:65
MythCoreContext::GetMasterServerStatusPort
int GetMasterServerStatusPort(void)
Returns the Master Backend status port If no master server status port has been defined in the databa...
Definition: mythcorecontext.cpp:997
UPNPScanner::StartFullScan
void StartFullScan(void)
Definition: upnpscanner.cpp:199
UPNPScanner::m_descriptionRequests
QMultiMap< QUrl, QNetworkReply * > m_descriptionRequests
Definition: upnpscanner.h:119
UPNPSubscription::Unsubscribe
void Unsubscribe(const QString &usn)
Definition: upnpsubscription.cpp:108
MythObservable::addListener
void addListener(QObject *listener)
Add a listener to the observable.
Definition: mythobservable.cpp:38
UPNPScanner::m_network
QNetworkAccessManager * m_network
Definition: upnpscanner.h:116
UPNPScanner::GetServerContent
void GetServerContent(QString &usn, MediaServerItem *content, VideoMetadataListManager::metadata_list *list, meta_dir_node *node)
Definition: upnpscanner.cpp:350
UPNPSubscription::Subscribe
std::chrono::seconds Subscribe(const QString &usn, const QUrl &url, const QString &path)
Definition: upnpsubscription.cpp:75
UPNPScanner::Stop
void Stop(void)
Definition: upnpscanner.cpp:453
UPNPScanner::m_updateTimer
QTimer * m_updateTimer
Definition: upnpscanner.h:122
UPNPScanner::ScheduleRenewal
void ScheduleRenewal(const QString &usn, std::chrono::seconds timeout)
Creates a QTimer to trigger a subscription renewal for a given media server.
Definition: upnpscanner.cpp:981
UPNPScanner
Definition: upnpscanner.h:45
UPNPScanner::FindResult
QDomDocument * FindResult(const QDomNode &n, uint &num, uint &total, uint &updateid)
Definition: upnpscanner.cpp:1185
mythlogging.h
meta_dir_node::addSubDir
smart_dir_node addSubDir(const QString &subdir, const QString &name="", const QString &host="", const QString &prefix="", const QVariant &data=QVariant())
Definition: videometadatalistmanager.cpp:335
MythCoreContext::GetMasterServerIP
QString GetMasterServerIP(void)
Returns the Master Backend IP address If the address is an IPv6 address, the scope Id is removed.
Definition: mythcorecontext.cpp:970
UPNPScanner::m_subscription
UPNPSubscription * m_subscription
Definition: upnpscanner.h:113
UPNPScanner::ServerList
QMap< QString, QString > ServerList(void)
Definition: upnpscanner.cpp:396
MThread::qthread
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Definition: mthread.cpp:233
UPNPScanner::RemoveServer
void RemoveServer(const QString &usn)
Definition: upnpscanner.cpp:959
UPNPScanner::AddServer
void AddServer(const QString &usn, const QString &url)
Definition: upnpscanner.cpp:924
UPNPScanner::m_scanComplete
bool m_scanComplete
Definition: upnpscanner.h:128
UpnpMediaServer::ResetContent
bool ResetContent(int new_id)
Definition: upnpscanner.cpp:102
UPNPScanner::ParseBrowse
void ParseBrowse(const QUrl &url, QNetworkReply *reply)
Definition: upnpscanner.cpp:997
UPNPScanner::SendBrowseRequest
void SendBrowseRequest(const QUrl &url, const QString &objectid)
Definition: upnpscanner.cpp:878
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
MediaServerItem::Find
MediaServerItem * Find(QString &id)
Definition: upnpscanner.cpp:50
clamp
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:204
UPNPScanner::gUPNPScannerThread
static MThread * gUPNPScannerThread
Definition: upnpscanner.h:110
MAX_ATTEMPTS
static constexpr uint8_t MAX_ATTEMPTS
Definition: upnpscanner.cpp:25
UPNPScanner::replyFinished
void replyFinished(QNetworkReply *reply)
Definition: upnpscanner.cpp:588
MediaServerItem::Reset
void Reset(void)
Definition: upnpscanner.cpp:77
UpnpMediaServer::UpnpMediaServer
UpnpMediaServer(QUrl URL)
Definition: upnpscanner.cpp:95
MythInfoMapEvent
Definition: mythevent.h:128
UpnpMediaServer::m_eventSubPath
QString m_eventSubPath
Definition: upnpscanner.cpp:117
UPNPScanner::ParseDescription
bool ParseDescription(const QUrl &url, QNetworkReply *reply)
Definition: upnpscanner.cpp:1245
meta_data_node
Definition: videometadatalistmanager.h:61
mythcorecontext.h
UPNPScanner::Start
void Start()
Definition: upnpscanner.cpp:415
UpnpMediaServer::UpnpMediaServer
UpnpMediaServer()
Definition: upnpscanner.cpp:90
UPNPScanner::Instance
static UPNPScanner * Instance(UPNPSubscription *sub=nullptr)
Definition: upnpscanner.cpp:161
UPNPScanner::ParseDevice
static void ParseDevice(QDomElement &element, QString &controlURL, QString &eventURL, QString &friendlyName)
Definition: upnpscanner.cpp:1387
UPNPScanner::~UPNPScanner
~UPNPScanner() override
Definition: upnpscanner.cpp:140
UPNPScanner::gUPNPScannerLock
static QRecursiveMutex * gUPNPScannerLock
Definition: upnpscanner.h:111
MThread
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:48
MediaServerItem::NextUnbrowsed
QString NextUnbrowsed(void)
Definition: upnpscanner.cpp:27
MThread::isRunning
bool isRunning(void) const
Definition: mthread.cpp:263
UPNPScanner::m_masterPort
int m_masterPort
Definition: upnpscanner.h:126
UPNPScanner::m_watchdogTimer
QTimer * m_watchdogTimer
Definition: upnpscanner.h:123
UPNPScanner::Update
void Update(void)
Definition: upnpscanner.cpp:512
UPNPScanner::Enable
static void Enable(bool enable, UPNPSubscription *sub=nullptr)
Definition: upnpscanner.cpp:149
VideoMetadataListManager::metadata_list
std::list< VideoMetadataPtr > metadata_list
Definition: videometadatalistmanager.h:14
MediaServerItem::m_scanned
bool m_scanned
Definition: upnpscanner.h:41
SSDP::AddListener
static void AddListener(QObject *listener)
Definition: ssdp.h:127
UPNPScanner::FindItems
void FindItems(const QDomNode &n, MediaServerItem &content, bool &resetparent)
Definition: upnpscanner.cpp:1103
azlyrics.info
dictionary info
Definition: azlyrics.py:7
UPNPScanner::CheckFailure
void CheckFailure(const QUrl &url)
Definition: upnpscanner.cpp:776
upnpscanner.h
UpnpMediaServer::m_renewalTimerId
int m_renewalTimerId
Definition: upnpscanner.cpp:121
UPNPScanner::BrowseNextContainer
void BrowseNextContainer(void)
Definition: upnpscanner.cpp:833
LOC
#define LOC
Definition: upnpscanner.cpp:22
UPNPScanner::ParseServiceList
static void ParseServiceList(QDomElement &element, QString &controlURL, QString &eventURL)
Definition: upnpscanner.cpp:1405
UPNPScanner::gUPNPScannerEnabled
static bool gUPNPScannerEnabled
Definition: upnpscanner.h:109
UpnpMediaServer::m_serverURL
QUrl m_serverURL
Definition: upnpscanner.cpp:114
UPNPScanner::m_masterHost
QString m_masterHost
Definition: upnpscanner.h:125
MythObservable::removeListener
void removeListener(QObject *listener)
Remove a listener to the observable.
Definition: mythobservable.cpp:55
UpnpMediaServer::m_subscribed
bool m_subscribed
Definition: upnpscanner.cpp:120
SSDP::RemoveListener
static void RemoveListener(QObject *listener)
Definition: ssdp.h:129
MediaServerItem::m_parentid
QString m_parentid
Definition: upnpscanner.h:38
UPNPSubscription::Remove
void Remove(const QString &usn)
Definition: upnpsubscription.cpp:162
simple_ref_ptr::get
T * get() const
Definition: quicksp.h:73
UPNPScanner::CheckStatus
void CheckStatus(void)
Definition: upnpscanner.cpp:560
UPNPScanner::GetMetadata
void GetMetadata(VideoMetadataListManager::metadata_list *list, meta_dir_node *node)
Fill the given metadata_list and meta_dir_node with the metadata of content retrieved from known medi...
Definition: upnpscanner.cpp:244
uint
unsigned int uint
Definition: freesurround.h:24
UPNPScanner::ParseService
static void ParseService(QDomElement &element, QString &controlURL, QString &eventURL)
Definition: upnpscanner.cpp:1419
UPNPScanner::Debug
void Debug(void)
Definition: upnpscanner.cpp:795