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