Switch to side-by-side view

--- 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