8#include <QRegularExpression>
18#define LOC QString("VBox: ")
21{
"http://{URL}/cgi-bin/HttpControl/HttpControlApp?OPTION=1&Method=QueryBoardInfo" };
23{
"http://{URL}/cgi-bin/HttpControl/HttpControlApp?OPTION=1&Method=GetXmltvChannelsList" \
24 "&FromChIndex=FirstChannel&ToChIndex=LastChannel&FilterBy=All" };
27static constexpr const char*
VBOX_URI {
"urn:schemas-upnp-org:device:MediaServer:1" };
28static constexpr const char*
VBOX_UDN {
"uuid:b7531642-0123-3210" };
33 const std::chrono::milliseconds milliSeconds {
SEARCH_TIME };
34 auto seconds = duration_cast<std::chrono::seconds>(milliSeconds);
39 if (!result.isEmpty())
43 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Using UPNP to search for Vboxes (%1 secs)")
44 .arg(seconds.count()));
52 while (totalTime.
elapsed() < milliSeconds)
54 std::this_thread::sleep_for(25ms);
55 auto ttl = duration_cast<std::chrono::seconds>(milliSeconds - totalTime.
elapsed());
56 if ((searchTime.
elapsed() > 249ms) && (ttl > 1s))
58 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"UPNP Search %1 secs")
76 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
"No UPnP VBoxes found");
80 int count = vboxes->
Count();
83 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
84 QString(
"Found %1 possible VBoxes").arg(count));
88 LOG(VB_GENERAL, LOG_ERR,
LOC +
89 "No UPnP VBoxes found, but SSDPCache::Instance()->Find() not NULL");
95 for (
auto *BE : std::as_const(map))
97 if (!BE->GetDeviceDesc())
99 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"GetDeviceDesc() failed for %1").arg(BE->GetFriendlyName()));
103 QString friendlyName = BE->GetDeviceDesc()->m_rootDevice.m_sFriendlyName;
104 QString ip = BE->GetDeviceDesc()->m_hostUrl.host();
105 QString udn = BE->GetDeviceDesc()->m_rootDevice.m_sUDN;
106 int port = BE->GetDeviceDesc()->m_hostUrl.port();
108 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"Found possible VBox at %1 (%2:%3)")
109 .arg(friendlyName, ip, QString::number(port)));
115 int startPos = friendlyName.indexOf(
'(');
116 int endPos = friendlyName.indexOf(
')');
118 if (startPos != -1 && endPos != -1)
119 id = friendlyName.mid(startPos + 1, endPos - startPos - 1);
130 for (
int x = 0; x < tuners.count(); x++)
134 const QString& tuner = tuners.at(x);
135 QString device = QString(
"%1 %2 %3").arg(
id, ip, tuner);
137 LOG(VB_GENERAL, LOG_INFO, QString(
"Found VBox - %1").arg(device));
156 QStringList devItems = dev.split(
"-");
158 if (devItems.size() != 3)
160 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Got malformed videodev %1").arg(dev));
164 QString
id = devItems.at(0).trimmed();
167 static const QRegularExpression ipRE { R
"(^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$)" };
168 auto match = ipRE.match(
id);
169 if (match.hasMatch())
175 for (
int x = 0; x < vboxes.count(); x++)
177 QStringList vboxItems = vboxes.at(x).split(
" ");
178 if (vboxItems.size() != 4)
180 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Got malformed probed device %1").arg(vboxes.at(x)));
184 const QString& vboxID = vboxItems.at(0);
185 QString vboxIP = vboxItems.at(1);
197 auto *xmlDoc =
new QDomDocument();
200 query.replace(
"{URL}",
m_url);
220 QStringList sList = requiredVersion.split(
'.');
223 if (sList.count() < 3 || !requiredVersion.startsWith(
"V"))
225 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Failed to parse required version from %1").arg(requiredVersion));
230 int requiredMajor = sList[1].toInt();
231 int requiredMinor = sList[2].toInt();
237 QDomElement elem = xmlDoc->documentElement();
246 if (sList.count() < 3 || !(
version.startsWith(
"VB.") ||
version.startsWith(
"VJ.")
249 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Failed to parse version from %1").arg(
version));
254 major = sList[1].toInt();
255 minor = sList[2].toInt();
260 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"CheckVersion - required: %1, actual: %2")
263 if (major < requiredMajor)
266 if (major == requiredMajor &&
minor < requiredMinor)
278 QDomElement elem = xmlDoc->documentElement();
284 for (
int x = 1; x <= noTuners; x++)
286 QString tuner =
getStrValue(elem, QString(
"Tuner%1").arg(x));
287 QString s = QString(
"%1 %2").arg(x).arg(tuner);
301 auto *xmlDoc =
new QDomDocument();
304 query.replace(
"{URL}",
m_url);
313 QDomNodeList chanNodes = xmlDoc->elementsByTagName(
"channel");
315 for (
int x = 0; x < chanNodes.count(); x++)
317 QDomElement chanElem = chanNodes.at(x).toElement();
318 QString xmltvid = chanElem.attribute(
"id",
"UNKNOWN_ID");
319 QString name =
getStrValue(chanElem,
"display-name", 0);
320 QString chanType =
getStrValue(chanElem,
"display-name", 1);
321 QString triplet =
getStrValue(chanElem,
"display-name", 2);
322 bool fta = (
getStrValue(chanElem,
"display-name", 3) ==
"Free");
323 QString lcn =
getStrValue(chanElem,
"display-name", 4);
324 uint serviceID = triplet.right(4).toUInt(
nullptr, 16);
326 QString transType =
"UNKNOWN";
327 QStringList slist = triplet.split(
'-');
328 uint networkID = slist[2].left(4).toUInt(
nullptr, 16);
329 uint transportID = slist[2].mid(4, 4).toUInt(
nullptr, 16);
330 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"NIT/TID/SID %1 %2 %3)").arg(networkID).arg(transportID).arg(serviceID));
334 if (slist.count() == 3)
335 transType = slist[0];
338 QDomNodeList iconNodes = chanElem.elementsByTagName(
"icon");
339 if (iconNodes.count())
341 QDomElement iconElem = iconNodes.at(0).toElement();
342 icon = iconElem.attribute(
"src",
"");
346 QDomNodeList urlNodes = chanElem.elementsByTagName(
"url");
347 if (urlNodes.count())
349 QDomElement urlElem = urlNodes.at(0).toElement();
350 url = urlElem.attribute(
"src",
"");
353 VBoxChannelInfo chanInfo(name, xmltvid, url, fta, chanType, transType, serviceID, networkID, transportID);
354 result->insert(lcn, chanInfo);
367#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
372 if (!xmlDoc->setContent(result,
false, &errorMsg, &errorLine, &errorColumn))
374 LOG(VB_GENERAL, LOG_ERR,
LOC +
375 QString(
"Error parsing: %1\nat line: %2 column: %3 msg: %4").
376 arg(query).arg(errorLine).arg(errorColumn).arg(errorMsg));
380 auto parseResult = xmlDoc->setContent(result);
383 LOG(VB_GENERAL, LOG_ERR,
LOC +
384 QString(
"Error parsing: %1\nat line: %2 column: %3 msg: %4")
385 .arg(query).arg(parseResult.errorLine)
386 .arg(parseResult.errorColumn).arg(parseResult.errorMessage));
392 QDomNodeList statusNodes = xmlDoc->elementsByTagName(
"Status");
394 if (!statusNodes.count())
395 statusNodes = xmlDoc->elementsByTagName(
"Error");
397 if (statusNodes.count())
399 QDomElement elem = statusNodes.at(0).toElement();
403 QString errorDesc =
getStrValue(elem,
"ErrorDescription");
408 LOG(VB_GENERAL, LOG_ERR,
LOC +
409 QString(
"API Error: %1 - %2, Query was: %3").arg(errorCode).arg(errorDesc, query));
421 QDomNodeList nodes = element.elementsByTagName(name);
422 if (!nodes.isEmpty())
424 if (index >= nodes.count())
426 QDomElement e = nodes.at(index).toElement();
437 return value.toInt();
442 for (QDomNode dname = element.firstChild(); !dname.isNull();
443 dname = dname.nextSibling())
445 QDomText
t = dname.toText();
A QElapsedTimer based timer to replace use of QTime as a timer.
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
void start(void)
starts measuring elapsed time.
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
void GetEntryMap(EntryMap &map)
Returns a copy of the EntryMap.
static SSDPCache * Instance()
SSDPCacheEntries * Find(const QString &sURI)
Finds the SSDPCacheEntries in the cache, returns nullptr when absent.
void PerformSearch(const QString &sST, std::chrono::seconds timeout=2s)
Send a SSDP discover multicast datagram.
bool checkVersion(QString &version)
static int getIntValue(const QDomElement &element, const QString &name, int index=0)
QDomDocument * getBoardInfo(void)
static QString getFirstText(QDomElement &element)
vbox_chan_map_t * getChannels(void)
QStringList getTuners(void)
returns a list of tuners in the format 'TUNERNO TUNERTYPE' eg '1 DVBT/T2'
static bool sendQuery(const QString &query, QDomDocument *xmlDoc)
static QStringList probeDevices(void)
static QString getStrValue(const QDomElement &element, const QString &name, int index=0)
bool checkConnection(void)
static QString getIPFromVideoDevice(const QString &dev)
static QStringList doUPNPSearch(void)
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QMap< QString, DeviceLocation * > EntryMap
Key == Unique Service Name (USN)
QMap< QString, VBoxChannelInfo > vbox_chan_map_t
static constexpr std::chrono::milliseconds SEARCH_TIME
static constexpr const char * VBOX_UDN
static constexpr const char * QUERY_CHANNELS
static constexpr const char * QUERY_BOARDINFO
static constexpr const char * VBOX_URI
static constexpr const char * VBOX_MIN_API_VERSION