Switch to side-by-side view

--- a/libupnpp/control/discovery.cxx
+++ b/libupnpp/control/discovery.cxx
@@ -30,6 +30,7 @@
 #include <upnp/upnptools.h>
 
 #include "libupnpp/upnpp_p.hxx"
+#include "libupnpp/upnpputils.hxx"
 #include "libupnpp/workqueue.hxx"
 #include "libupnpp/upnpplib.hxx"
 #include "libupnpp/log.hxx"
@@ -37,6 +38,8 @@
 #include "discovery.hxx"
 
 namespace UPnPClient {
+
+static UPnPDeviceDirectory *theDevDir;
 
 //#undef LOCAL_LOGINC
 //#define LOCAL_LOGINC 3
@@ -75,6 +78,10 @@
     string deviceId;
     int expires; // Seconds valid
 };
+
+// The workqueue on which callbacks from libupnp (cluCallBack()) queue
+// discovered object descriptors for processing by our dedicated
+// thread.
 static WorkQueue<DiscoveredTask*> discoveredQueue("DiscoveredQueue");
 
 // This gets called in a libupnp thread context for all asynchronous
@@ -84,9 +91,9 @@
 // It seems that this can get called by several threads. We have a
 // mutex just for clarifying the message printing, the workqueue is
 // mt-safe of course.
-static PTMutexInit cblock;
 static int cluCallBack(Upnp_EventType et, void* evp, void*)
 {
+    static PTMutexInit cblock;
     PTMutexLocker lock(cblock);
     LOGDEB1("discovery:cluCallBack: " << LibUPnP::evTypeAsString(et) << endl);
 
@@ -130,7 +137,28 @@
     return UPNP_E_SUCCESS;
 }
 
-// Descriptor for one device found on the network.
+// Our client can set up functions to be called when we process a new device.
+// This is used during startup, when the pool is not yet complete, to enable
+// finding and listing devices as soon as they appear.
+static vector<UPnPDeviceDirectory::Visitor> o_callbacks;
+static PTMutexInit o_callbacks_mutex;
+
+unsigned int UPnPDeviceDirectory::addCallback(UPnPDeviceDirectory::Visitor v)
+{
+    PTMutexLocker lock(o_callbacks_mutex);
+    o_callbacks.push_back(v);
+    return o_callbacks.size() - 1;
+}
+
+void UPnPDeviceDirectory::delCallback(unsigned int idx)
+{
+    PTMutexLocker lock(o_callbacks_mutex);
+    if (idx >= o_callbacks.size())
+        return;
+    o_callbacks.erase(o_callbacks.begin() + idx);
+}
+
+// Descriptor kept in the device pool for each device found on the network.
 class DeviceDescriptor {
 public:
     DeviceDescriptor(const string& url, const string& description,
@@ -209,10 +237,18 @@
             LOGDEB1("discoExplorer: found id [" << tsk->deviceId  << "]" 
                     << " name " << d.device.friendlyName 
                     << " devtype " << d.device.deviceType << endl);
-            PTMutexLocker lock(o_pool.m_mutex);
-            //LOGDEB1("discoExplorer: inserting device id "<< tsk->deviceId << 
-            //        " description: " << endl << d.device.dump() << endl);
-            o_pool.m_devices[tsk->deviceId] = d;
+            {
+                PTMutexLocker lock(o_pool.m_mutex);
+                //LOGDEB1("discoExplorer: inserting device id "<< tsk->deviceId
+                // <<  " description: " << endl << d.device.dump() << endl);
+                o_pool.m_devices[tsk->deviceId] = d;
+            }
+            {
+                PTMutexLocker lock(o_callbacks_mutex);
+                for (auto& cb: o_callbacks) {
+                    cb(d.device, UPnPServiceDesc());
+                }
+            }
         }
         delete tsk;
     }
@@ -252,6 +288,8 @@
 UPnPDeviceDirectory::UPnPDeviceDirectory(time_t search_window)
     : m_ok(false), m_searchTimeout(search_window), m_lastSearch(0)
 {
+    addCallback(std::bind(&UPnPDeviceDirectory::deviceFound, this, _1, _2));
+
     if (!discoveredQueue.start(1, discoExplorer, 0)) {
         m_reason = "Discover work queue start failed";
         return;
@@ -296,8 +334,6 @@
     return true;
 }
 
-static UPnPDeviceDirectory *theDevDir;
-
 UPnPDeviceDirectory *UPnPDeviceDirectory::getTheDir(time_t search_window)
 {
     if (theDevDir == 0)
@@ -325,9 +361,9 @@
     //LOGDEB("UPnPDeviceDirectory::traverse" << endl);
     if (m_ok == false)
         return false;
-
-    if (getRemainingDelay() > 0)
-        sleep(getRemainingDelay());
+    int secs = getRemainingDelay();
+    if (secs > 0)
+        sleep(secs);
 
     // Has locking, do it before our own lock
     expireDevices();
@@ -343,5 +379,71 @@
     return true;
 }
 
+static PTMutexInit devWaitLock;
+static pthread_cond_t devWaitCond = PTHREAD_COND_INITIALIZER;
+
+bool UPnPDeviceDirectory::deviceFound(const UPnPDeviceDesc&, 
+                                      const UPnPServiceDesc&)
+{
+    PTMutexLocker lock(devWaitLock);
+    pthread_cond_broadcast(&devWaitCond);
+    return true;
+}
+
+bool UPnPDeviceDirectory::getDevBySelector(bool cmp(const UPnPDeviceDesc& ddesc,
+                                                    const string&), 
+                                           const string& value,
+                                           UPnPDeviceDesc& ddesc)
+{
+    // Has locking, do it before our own lock
+    expireDevices();
+
+    struct timespec wkuptime;
+    long long nanos = getRemainingDelay() * 1000*1000*1000;
+    clock_gettime(CLOCK_REALTIME, &wkuptime);
+    UPnPP::timespec_addnanos(&wkuptime, nanos);
+    do {
+        PTMutexLocker lock(devWaitLock);
+        {
+            PTMutexLocker lock(o_pool.m_mutex);
+            for (auto& dde : o_pool.m_devices) {
+                if (!cmp(dde.second.device, value)) {
+                    ddesc = dde.second.device;
+                    return true;
+                }
+            }
+        }
+
+        if (nanos > 0) {
+            pthread_cond_timedwait(&devWaitCond, lock.getMutex(), &wkuptime);
+        }
+    } while (getRemainingDelay() > 0);
+    return false;
+}
+
+static bool cmpFName(const UPnPDeviceDesc& ddesc, const string& fname)
+{
+    return ddesc.friendlyName.compare(fname);
+}
+
+bool UPnPDeviceDirectory::getDevByFName(const string& fname, 
+                                        UPnPDeviceDesc& ddesc)
+{
+    return getDevBySelector(cmpFName, fname, ddesc);
+}
+
+static bool cmpUDN(const UPnPDeviceDesc& ddesc, const string& value)
+{
+    return ddesc.UDN.compare(value);
+}
+
+bool UPnPDeviceDirectory::getDevByUDN(const string& value, 
+                                      UPnPDeviceDesc& ddesc)
+{
+    return getDevBySelector(cmpUDN, value, ddesc);
+}
+
+
+
 } // namespace UPnPClient