--- a/libupnpp/control/discovery.cxx
+++ b/libupnpp/control/discovery.cxx
@@ -45,12 +45,26 @@
namespace UPnPClient {
+// The singleton instance pointer
static UPnPDeviceDirectory *theDevDir;
-
-// To avoid changing the abi, the m_lastSearch private class member
-// was kept but superceded by a static value (we're a singleton
-// anyway).
+// Are we working?
+static bool o_ok{false};
+// If not, why?
+static string o_reason;
+// Search window (seconds)
+static time_t o_searchTimeout{2};
+// Last time we broadcasted a search request
static std::chrono::steady_clock::time_point o_lastSearch;
+// Directory initialized at least once ?
+static bool o_initialSearchDone{false};
+
+// Start UPnP search and record start of window
+static bool search();
+// This is called by the thread which processes the device events
+// when a new device appears. It wakes up any thread waiting for a
+// device.
+static bool deviceFound(const UPnPDeviceDesc&, const UPnPServiceDesc&);
+
static string cluDiscoveryToStr(const struct Upnp_Discovery *disco)
{
@@ -148,7 +162,7 @@
}
}
- LOGDEB1("discoExplorer: downloading " << tp->url << endl);
+ LOGDEB1("discovery:cluCallback:: downloading " << tp->url << endl);
if (!downloadUrlWithCurl(tp->url, tp->description, 5)) {
LOGERR("discovery:cllb: downloadUrlWithCurl error for: " <<
tp->url << endl);
@@ -166,6 +180,7 @@
}
if (!discoveredQueue.put(tp)) {
+ delete tp;
LOGERR("discovery:cllb: queue.put failed\n");
}
break;
@@ -176,6 +191,7 @@
LOGDEB1("discovery:cllB:BYEBYE: " << cluDiscoveryToStr(disco) << endl);
DiscoveredTask *tp = new DiscoveredTask(0, disco);
if (!discoveredQueue.put(tp)) {
+ delete tp;
LOGERR("discovery:cllb: queue.put failed\n");
}
break;
@@ -195,11 +211,18 @@
// finding and listing devices as soon as they appear.
static vector<UPnPDeviceDirectory::Visitor> o_callbacks;
static std::mutex o_callbacks_mutex;
+static bool simpleTraverse(UPnPDeviceDirectory::Visitor visit);
+static bool simpleVisit(UPnPDeviceDesc&, UPnPDeviceDirectory::Visitor);
unsigned int UPnPDeviceDirectory::addCallback(UPnPDeviceDirectory::Visitor v)
{
std::unique_lock<std::mutex> lock(o_callbacks_mutex);
o_callbacks.push_back(v);
+ // People use this method to avoid waiting for the initial
+ // delay. Return all data which we already have ! Else the
+ // quick-responding devices won't be found before the
+ // delay ends and the user finally calls traverse().
+ simpleTraverse(v);
return o_callbacks.size() - 1;
}
@@ -242,7 +265,7 @@
// Worker routine for the discovery queue. Get messages about devices
// appearing and disappearing, and update the directory pool
// accordingly.
-void *UPnPDeviceDirectory::discoExplorer(void *)
+static void *discoExplorer(void *)
{
for (;;) {
DiscoveredTask *tsk = 0;
@@ -287,10 +310,7 @@
{
std::unique_lock<std::mutex> lock(o_callbacks_mutex);
for (auto& cbp : o_callbacks) {
- (cbp)(d.device, UPnPServiceDesc());
- for (auto& it1 : d.device.embedded) {
- (cbp)(it1, UPnPServiceDesc());
- }
+ simpleVisit(d.device, cbp);
}
}
}
@@ -300,7 +320,7 @@
// Look at the devices and get rid of those which have not been seen
// for too long. We do this when listing the top directory
-void UPnPDeviceDirectory::expireDevices()
+static void expireDevices()
{
LOGDEB1("discovery: expireDevices:" << endl);
std::unique_lock<std::mutex> lock(o_pool.m_mutex);
@@ -335,18 +355,18 @@
// This means that you have to wait for the specified period before
// the results are complete.
UPnPDeviceDirectory::UPnPDeviceDirectory(time_t search_window)
- : m_ok(false), m_searchTimeout(int(search_window))
-{
- addCallback(std::bind(&UPnPDeviceDirectory::deviceFound, this, _1, _2));
+{
+ o_searchTimeout = search_window;
+ addCallback(std::bind(&deviceFound, _1, _2));
if (!discoveredQueue.start(1, discoExplorer, 0)) {
- m_reason = "Discover work queue start failed";
+ o_reason = "Discover work queue start failed";
return;
}
std::this_thread::yield();
LibUPnP *lib = LibUPnP::getLibUPnP();
if (lib == 0) {
- m_reason = "Can't get lib";
+ o_reason = "Can't get lib";
return;
}
lib->registerHandler(UPNP_DISCOVERY_SEARCH_RESULT, cluCallBack, this);
@@ -355,22 +375,32 @@
lib->registerHandler(UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE,
cluCallBack, this);
- m_ok = search();
-}
-
-bool UPnPDeviceDirectory::search()
+ o_ok = search();
+}
+
+bool UPnPDeviceDirectory::ok()
+{
+ return o_ok;
+}
+
+const string UPnPDeviceDirectory::getReason()
+{
+ return o_reason;
+}
+
+static bool search()
{
LOGDEB1("UPnPDeviceDirectory::search" << endl);
if (std::chrono::steady_clock::now() - o_lastSearch <
- std::chrono::seconds(m_searchTimeout)) {
+ std::chrono::seconds(o_searchTimeout)) {
LOGDEB1("UPnPDeviceDirectory: last search too close\n");
return true;
}
LibUPnP *lib = LibUPnP::getLibUPnP();
if (lib == 0) {
- m_reason = "Can't get lib";
+ o_reason = "Can't get lib";
return false;
}
@@ -384,11 +414,11 @@
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
LOGDEB1("UPnPDeviceDirectory::search: calling upnpsearchasync" << endl);
- int code1 = UpnpSearchAsync(lib->getclh(), m_searchTimeout, cp, lib);
+ int code1 = UpnpSearchAsync(lib->getclh(), o_searchTimeout, cp, lib);
if (code1 != UPNP_E_SUCCESS) {
- m_reason = LibUPnP::errAsString("UpnpSearchAsync", code1);
+ o_reason = LibUPnP::errAsString("UpnpSearchAsync", code1);
LOGERR("UPnPDeviceDirectory::search: UpnpSearchAsync failed: " <<
- m_reason << endl);
+ o_reason << endl);
}
}
o_lastSearch = std::chrono::steady_clock::now();
@@ -406,12 +436,18 @@
void UPnPDeviceDirectory::terminate()
{
+ LibUPnP *lib = LibUPnP::getLibUPnP();
+ if (lib) {
+ lib->registerHandler(UPNP_DISCOVERY_SEARCH_RESULT, 0, 0);
+ lib->registerHandler(UPNP_DISCOVERY_ADVERTISEMENT_ALIVE, 0, 0);
+ lib->registerHandler(UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE, 0, 0);
+ }
discoveredQueue.setTerminateAndWait();
}
time_t UPnPDeviceDirectory::getRemainingDelayMs()
{
- auto remain = std::chrono::seconds(m_searchTimeout) -
+ auto remain = std::chrono::seconds(o_searchTimeout) -
(std::chrono::steady_clock::now() - o_lastSearch);
// Let's give them a grace delay beyond the search window
remain += std::chrono::milliseconds(200);
@@ -424,7 +460,7 @@
time_t UPnPDeviceDirectory::getRemainingDelay()
{
- time_t millis = getRemainingDelayMs();
+ time_t millis = getTheDir()->getRemainingDelayMs();
if (millis <= 0)
return 0;
return millis >= 1000 ? millis / 1000 : 1;
@@ -433,64 +469,86 @@
static std::mutex devWaitLock;
static std::condition_variable devWaitCond;
+// Call user function on one device (for all services)
+static bool simpleVisit(UPnPDeviceDesc& dev,
+ UPnPDeviceDirectory::Visitor visit)
+{
+ for (auto& it1 : dev.services) {
+ if (!visit(dev, it1)) {
+ return false;
+ }
+ }
+ for (auto& it1 : dev.embedded) {
+ for (auto& it2 : it1.services) {
+ if (!visit(it1, it2)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+// Walk the device list and call simpleVisit() on each.
+static bool simpleTraverse(UPnPDeviceDirectory::Visitor visit)
+{
+ std::unique_lock<std::mutex> lock(o_pool.m_mutex);
+
+ for (auto& it : o_pool.m_devices) {
+ if (!simpleVisit(it.second.device, visit)) {
+ return false;
+ }
+ }
+ return true;
+}
+
bool UPnPDeviceDirectory::traverse(UPnPDeviceDirectory::Visitor visit)
{
//LOGDEB("UPnPDeviceDirectory::traverse" << endl);
- if (m_ok == false)
+ if (!o_ok)
return false;
- // Wait until the discovery delay is over. We need to loop because of
- // spurious wakeups each time a new device is discovered. We could use a
- // separate cv or another way of sleeping instead.
- for (;;) {
+ // Wait until the discovery delay is over. We need to loop because
+ // of spurious wakeups each time a new device is discovered. We
+ // could use a separate cv or another way of sleeping instead. We
+ // only do this once, after which we're sure that the initial
+ // discovery is done and that the directory is supposedly up to
+ // date. There is no reason to wait during further searches. We
+ // may wait for nothing once but it's simpler than detecting the
+ // end of the actual initial discovery.
+ for (;!o_initialSearchDone;) {
std::unique_lock<std::mutex> lock(devWaitLock);
int ms;
if ((ms = getRemainingDelayMs()) > 0) {
devWaitCond.wait_for(lock, chrono::milliseconds(ms));
} else {
+ o_initialSearchDone = true;
break;
}
}
// Has locking, do it before our own lock
expireDevices();
-
- std::unique_lock<std::mutex> lock(o_pool.m_mutex);
-
- for (auto& it : o_pool.m_devices) {
- for (auto& it1 : it.second.device.services) {
- if (!visit(it.second.device, it1)) {
- return false;
- }
- }
- for (auto& it1 : it.second.device.embedded) {
- for (auto& it2 : it1.services) {
- if (!visit(it1, it2)) {
- return false;
- }
- }
- }
- }
- return true;
-}
-
-bool UPnPDeviceDirectory::deviceFound(const UPnPDeviceDesc&,
- const UPnPServiceDesc&)
+ return simpleTraverse(visit);
+}
+
+static bool deviceFound(const UPnPDeviceDesc&, const UPnPServiceDesc&)
{
devWaitCond.notify_all();
return true;
}
-bool UPnPDeviceDirectory::getDevBySelector(bool cmp(const UPnPDeviceDesc& ddesc,
- const string&),
- const string& value, UPnPDeviceDesc& ddesc)
+// Lookup a device in the pool. If not found and a search is active,
+// use a cond_wait to wait for device events (awaken by deviceFound).
+static bool getDevBySelector(
+ bool cmp(const UPnPDeviceDesc& ddesc, const string&),
+ const string& value, UPnPDeviceDesc& ddesc)
{
// Has locking, do it before our own lock
expireDevices();
for (;;) {
std::unique_lock<std::mutex> lock(devWaitLock);
- int ms = getRemainingDelayMs();
+ int ms = UPnPDeviceDirectory::getTheDir()->getRemainingDelayMs();
{
std::unique_lock<std::mutex> lock(o_pool.m_mutex);
for (auto& it : o_pool.m_devices) {
@@ -538,6 +596,24 @@
return getDevBySelector(cmpUDN, value, ddesc);
}
+bool UPnPDeviceDirectory::getDescriptionDocuments(
+ const string &uidOrFriendly, string& deviceXML,
+ unordered_map<string, string>& srvsXML)
+{
+ UPnPDeviceDesc ddesc;
+ if (!getDevByUDN(uidOrFriendly, ddesc) &&
+ !getDevByFName(uidOrFriendly, ddesc)) {
+ return false;
+ }
+ deviceXML = ddesc.XMLText;
+ for (const auto& entry : ddesc.services) {
+ srvsXML[entry.serviceId] = "";
+ UPnPServiceDesc::Parsed parsed;
+ entry.fetchAndParseDesc(ddesc.URLBase, parsed,
+ &srvsXML[entry.serviceId]);
+ }
+ return true;
+}
} // namespace UPnPClient