separation cleanup

Jean-Francois Dockes Jean-Francois Dockes 2014-10-12

<< < 1 2 3 (Page 3 of 3)
copied src/control/service.cxx -> libupnpp/control/service.cxx
copied src/control/service.hxx -> libupnpp/upnpplib.hxx
copied src/device/device.cxx -> libupnpp/expatmm.hxx
copied src/device/device.hxx -> libupnpp/device/vdir.cxx
copied src/device/vdir.cxx -> libupnpp/soaphelp.cxx
copied src/device/vdir.hxx -> libupnpp/device/vdir.hxx
copied src/base64.cxx -> libupnpp/base64.cxx
copied src/base64.hxx -> libupnpp/base64.hxx
copied src/config.h.in -> libupnpp/config.h.in
copied src/expatmm.hxx -> libupnpp/workqueue.hxx
copied src/getsyshwaddr.c -> libupnpp/getsyshwaddr.c
copied src/getsyshwaddr.h -> libupnpp/getsyshwaddr.h
copied src/ixmlwrap.cxx -> libupnpp/ptmutex.hxx
copied src/ixmlwrap.hxx -> libupnpp/log.cxx
copied src/log.cxx -> libupnpp/upnpavutils.cxx
copied src/log.hxx -> libupnpp/log.hxx
copied src/md5.cxx -> libupnpp/md5.cxx
copied src/md5.hxx -> libupnpp/md5.hxx
copied src/ptmutex.hxx -> libupnpp/upnpp_p.hxx
copied src/upnpavutils.cxx -> libupnpp/upnpavutils.hxx
copied src/upnpavutils.hxx -> libupnpp/upnpputils.hxx
src/control/service.hxx to libupnpp/upnpplib.hxx
--- a/src/control/service.hxx
+++ b/libupnpp/upnpplib.hxx
@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 J.F.Dockes
+/* Copyright (C) 2013 J.F.Dockes
  *       This program is free software; you can redistribute it and/or modify
  *       it under the terms of the GNU General Public License as published by
  *       the Free Software Foundation; either version 2 of the License, or
@@ -14,156 +14,126 @@
  *       Free Software Foundation, Inc.,
  *       59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
-#ifndef _SERVICE_H_X_INCLUDED_
-#define _SERVICE_H_X_INCLUDED_
+#ifndef _LIBUPNP_H_X_INCLUDED_
+#define _LIBUPNP_H_X_INCLUDED_
 
-#include <upnp/upnp.h>                  // for UPNP_E_BAD_RESPONSE, etc
+#include <upnp/upnp.h>                  // for Upnp_EventType, Upnp_FunPtr, etc
 
-#include <functional>                   // for function
-#include <iostream>                     // for basic_ostream, operator<<, etc
-#include <string>                       // for string, operator<<, etc
-#include <unordered_map>                // for unordered_map
-#include <vector>                       // for vector
+#include <map>                          // for map
+#include <string>                       // for string
 
-#include "libupnpp/control/cdircontent.hxx"  // for UPnPDirObject
-#include "libupnpp/log.hxx"             // for LOGERR
-#include "libupnpp/soaphelp.hxx"        // for SoapDecodeOutput, etc
+#include "ptmutex.hxx"                  // for PTMutexInit
 
-namespace UPnPClient { class UPnPDeviceDesc; }
-namespace UPnPClient { class UPnPServiceDesc; }
+namespace UPnPP {
 
-using namespace UPnPP;
+/** Our link to libupnp. Initialize and keep the handle around */
+class LibUPnP {
+public:
+    ~LibUPnP();
 
-namespace UPnPClient {
+    /** Retrieve the singleton LibUPnP object 
+     *
+     * This initializes libupnp, possibly setting an address and port, possibly
+     * registering a client if serveronly is false.
+     *
+     * @param serveronly no client init
+     * @param hwaddr returns the hardware address for the specified network 
+     *   interface, or the first one is ifname is empty. If the IP address is
+     *   specified instead of the interface name, the hardware address
+     *   returned is not necessarily the one matching the IP.
+     * @param ifname if not empty, network interface to use. Translated to
+     *   IP address for the UpnpInit() call.
+     * @param ip if not empty, IP address to use. Only used if ifname is empty.
+     * @param port   port parameter to UpnpInit() (0 for default).
+     * @return 0 for failure.
+     */
+    static LibUPnP* getLibUPnP(bool serveronly = false, std::string* hwaddr = 0,
+                               const std::string ifname = std::string(),
+                               const std::string ip = std::string(),
+                               unsigned short port = 0);
 
-/** To be implemented by upper-level client code for event
- * reporting. Runs in an event thread. This could for example be
- * implemented by a Qt Object to generate events for the GUI.
- */
-class VarEventReporter {
-public:
-    virtual ~VarEventReporter() {};
-    // Using char * to avoid any issue with strings and concurrency
-    virtual void changed(const char *nm, int val)  = 0;
-    virtual void changed(const char *nm, const char *val) = 0;
-    // Used for track metadata (parsed as content directory entry). Not always
-    // needed.
-    virtual void changed(const char */*nm*/, UPnPDirObject /*meta*/) {};
-    // Used by ohplaylist. Not always needed
-    virtual void changed(const char */*nm*/, std::vector<int> /*ids*/) {};
+    /** Set libupnp log file name and activate logging.
+     *
+     * @param fn file name to use. Use empty string to turn logging off
+     */
+    enum LogLevel {LogLevelNone, LogLevelError, LogLevelDebug};
+    bool setLogFileName(const std::string& fn, LogLevel level = LogLevelError);
+    bool setLogLevel(LogLevel level);
+
+    /** Set max library buffer size for reading content from servers. */
+    void setMaxContentLength(int bytes);
+
+    /** Check state after initialization */
+    bool ok() const
+    {
+        return m_ok;
+    }
+
+    /** Retrieve init error if state not ok */
+    int getInitError() const
+    {
+        return m_init_error;
+    }
+
+    /** Build a unique persistent UUID for a root device. This uses a hash
+        of the input name (e.g.: friendlyName), and the host Ethernet address */
+    static std::string makeDevUUID(const std::string& name, 
+                                   const std::string& hw);
+
+    /** Translate libupnp integer error code (UPNP_E_XXX) to string */
+    static std::string errAsString(const std::string& who, int code);
+
+/////////////////////////////////////////////////////////////////////////////
+    /* The methods which follow are normally for use by the 
+     * intermediate layers in libupnpp, such as the base device class
+     * or the server directory, end-user code should not need them in
+     * general.
+     */
+
+    /** Specify function to be called on given UPnP event. This will happen
+     * in the libupnp thread context. 
+     */
+    void registerHandler(Upnp_EventType et, Upnp_FunPtr handler, void *cookie);
+
+    /** Translate libupnp event type as string */
+    static std::string evTypeAsString(Upnp_EventType);
+
+    int setupWebServer(const std::string& description, UpnpDevice_Handle *dvh);
+
+    UpnpClient_Handle getclh()
+    {
+        return m_clh;
+    }
+private:
+
+    // A Handler object records the data from registerHandler.
+    class Handler {
+    public:
+        Handler()
+            : handler(0), cookie(0) {}
+        Handler(Upnp_FunPtr h, void *c)
+            : handler(h), cookie(c) {}
+        Upnp_FunPtr handler;
+        void *cookie;
+    };
+
+        
+    LibUPnP(bool serveronly, std::string *hwaddr, 
+            const std::string ifname, const std::string ip,
+            unsigned short port);
+
+    LibUPnP(const LibUPnP &);
+    LibUPnP& operator=(const LibUPnP &);
+
+    static int o_callback(Upnp_EventType, void *, void *);
+
+    bool m_ok;
+    int  m_init_error;
+    UpnpClient_Handle m_clh;
+    PTMutexInit m_mutex;
+    std::map<Upnp_EventType, Handler> m_handlers;
 };
 
-typedef 
-std::function<void (const std::unordered_map<std::string, std::string>&)> 
-evtCBFunc;
+} // namespace UPnPP
 
-class Service {
-public:
-    /** Construct by copying data from device and service objects.
-     */
-    Service(const UPnPDeviceDesc& device,
-            const UPnPServiceDesc& service, bool doSubscribe = true);
-
-    /** An empty one */
-    Service() : m_reporter(0) {}
-
-    virtual ~Service();
-
-    /** Retrieve my root device "friendly name". */
-    std::string getFriendlyName() const {return m_friendlyName;}
-
-    /** Return my root device id */
-    std::string getDeviceId() const {return m_deviceId;}
-
-    virtual int runAction(const SoapEncodeInput& args, SoapDecodeOutput& data);
-
-    virtual VarEventReporter *getReporter()
-    {
-        return m_reporter;
-    }
-
-    virtual void installReporter(VarEventReporter* reporter)
-    {
-        m_reporter = reporter;
-    }
-
-    // Can't copy these because this does not make sense for the
-    // member function callback.
-    Service(Service const&) = delete;
-    Service& operator=(Service const&) = delete;
-
-protected:
-
-    /** Registered callbacks for the service objects. The map is
-     * indexed by m_SID, the subscription id which was obtained by
-     * each object when subscribing to receive the events for its
-     * device. The map allows the static function registered with
-     * libupnp to call the appropriate object method when it receives
-     * an event. */
-    static std::unordered_map<std::string, evtCBFunc> o_calls;
-
-    /** Used by a derived class to register its callback method. This
-     * creates an entry in the static map, using m_SID, which was
-     * obtained by subscribe() during construction 
-     */
-    void registerCallback(evtCBFunc c);
-
-    /** Upper level client code event callbacks */
-    VarEventReporter *m_reporter;
-
-    std::string m_actionURL;
-    std::string m_eventURL;
-    std::string m_serviceType;
-    std::string m_deviceId;
-    std::string m_friendlyName;
-    std::string m_manufacturer;
-    std::string m_modelName;
-
-    int runTrivialAction(const std::string& nm) {
-        SoapEncodeInput args(m_serviceType, nm);
-        SoapDecodeOutput data;
-        return runAction(args, data);
-    }
-
-    template <class T> int runSimpleGet(const std::string& actnm, 
-                                        const std::string& valnm,
-                                        T *valuep) {
-        SoapEncodeInput args(m_serviceType, actnm);
-        SoapDecodeOutput data;
-        int ret = runAction(args, data);
-        if (ret != UPNP_E_SUCCESS) {
-            return ret;
-        }
-        if (!data.get(valnm.c_str(), valuep)) {
-            LOGERR("Service::runSimpleAction: " << actnm << 
-                   " missing " << valnm << " in response" << std::endl);
-            return UPNP_E_BAD_RESPONSE;
-        }
-        return 0;
-    }
-    template <class T> int runSimpleAction(const std::string& actnm, 
-                                           const std::string& valnm,
-                                           T value) {
-        SoapEncodeInput args(m_serviceType, actnm);
-        args(valnm, SoapHelp::val2s(value));
-        SoapDecodeOutput data;
-        return runAction(args, data);
-    }
-
-private:
-    /** Only actually does something on the first call, to register our
-     * (static) library callback */
-    static bool initEvents();
-    /** The static event callback given to libupnp */
-    static int srvCB(Upnp_EventType et, void* vevp, void*);
-    /* Tell the UPnP device (through libupnp) that we want to receive
-       its events. This is called during construction and sets m_SID */
-    virtual bool subscribe();
-    virtual bool unSubscribe();
-
-    Upnp_SID    m_SID; /* Subscription Id */
-};
-
-} // namespace UPnPClient
-
-#endif /* _SERVICE_H_X_INCLUDED_ */
+#endif /* _LIBUPNP.H_X_INCLUDED_ */
src/device/device.cxx to libupnpp/expatmm.hxx
--- a/libupnpp/device/device.cxx
+++ b/libupnpp/expatmm.hxx
@@ -1,450 +1,312 @@
-/* Copyright (C) 2014 J.F.Dockes
- *	 This program is free software; you can redistribute it and/or modify
- *	 it under the terms of the GNU General Public License as published by
- *	 the Free Software Foundation; either version 2 of the License, or
- *	 (at your option) any later version.
+/*
+ * ExpatMM - C++ Wrapper for Expat available at http://expat.sourceforge.net/
+ * Copyright (c) 2006, 2007, 2008, 2009 IntelliTree Solutions llc
+ * Author: Coleman Kane <ckane@intellitree.com>
  *
- *	 This program is distributed in the hope that it will be useful,
- *	 but WITHOUT ANY WARRANTY; without even the implied warranty of
- *	 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *	 GNU General Public License for more details.
+ * Mutilated and forced into single-file solution by <jf@dockes.org>
+ * Copyright (c) 2013 J.F. Dockes
  *
- *	 You should have received a copy of the GNU General Public License
- *	 along with this program; if not, write to the
- *	 Free Software Foundation, Inc.,
- *	 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, please feel free
+ * to contact the author listed above.
  */
-#include "config.h"
-
-#include "device.hxx"
-
-#include <errno.h>                      // for ETIMEDOUT, errno
-#include <sys/time.h>                   // for CLOCK_REALTIME
-#include <time.h>                       // for timespec, clock_gettime
-
-#include <iostream>                     // for endl, operator<<, etc
-#include <utility>                      // for pair
-
-#include "libupnpp/log.hxx"             // for LOGERR, LOGFAT, LOGDEB, etc
-#include "libupnpp/ixmlwrap.hxx"
-#include "libupnpp/upnpplib.hxx"        // for LibUPnP
-#include "libupnpp/upnpputils.hxx"      // for timespec_addnanos
-
-#include "vdir.hxx"                     // for VirtualDir
-
-using namespace std;
-using namespace UPnPP;
-
-namespace UPnPProvider {
-
-unordered_map<std::string, UpnpDevice *> UpnpDevice::o_devices;
-PTMutexInit o_devices_lock;
-
-static bool vectorstoargslists(const vector<string>& names, 
-                               const vector<string>& values,
-                               vector<string>& qvalues,
-                               vector<const char *>& cnames,
-                               vector<const char *>& cvalues)
-{
-    if (names.size() != values.size()) {
-        LOGERR("vectorstoargslists: bad sizes" << endl);
-        return false;
-    }
-
-    cnames.reserve(names.size());
-    qvalues.clear();
-    qvalues.reserve(values.size());
-    cvalues.reserve(values.size());
-    for (unsigned int i = 0; i < values.size(); i++) {
-        cnames.push_back(names[i].c_str());
-        qvalues.push_back(SoapHelp::xmlQuote(values[i]));
-        cvalues.push_back(qvalues[i].c_str());
-        //LOGDEB("Edata: " << cnames[i] << " -> " << cvalues[i] << endl);
-    }
-    return true;
-}
-
-static const int expiretime = 3600;
-
-UpnpDevice::UpnpDevice(const string& deviceId, 
-                       const unordered_map<string, VDirContent>& files)
-    : m_deviceId(deviceId), m_needExit(false), m_evloopcond(PTHREAD_COND_INITIALIZER)
-{
-    //LOGDEB("UpnpDevice::UpnpDevice(" << m_deviceId << ")" << endl);
-
-    m_lib = LibUPnP::getLibUPnP(true);
-    if (!m_lib) {
-        LOGFAT(" Can't get LibUPnP" << endl);
-        return;
-    }
-    if (!m_lib->ok()) {
-        LOGFAT("Lib init failed: " <<
-               m_lib->errAsString("main", m_lib->getInitError()) << endl);
-        m_lib = 0;
-        return;
-    }
-
-    {
-        PTMutexLocker lock(o_devices_lock);
-        if (o_devices.empty()) {
-            // First call: init callbacks
-            m_lib->registerHandler(UPNP_CONTROL_ACTION_REQUEST, sCallBack, this);
-            m_lib->registerHandler(UPNP_CONTROL_GET_VAR_REQUEST, sCallBack, this);
-            m_lib->registerHandler(UPNP_EVENT_SUBSCRIPTION_REQUEST, sCallBack,this);
-        }
-        o_devices[m_deviceId] = this;
-    }
-
-    VirtualDir* theVD = VirtualDir::getVirtualDir();
-    if (theVD == 0) {
-        LOGFAT("UpnpDevice::UpnpDevice: can't get VirtualDir" << endl);
-        return;
-    }
-
-    unordered_map<string, VDirContent>::const_iterator it = 
-        files.find("description.xml");
-    if (it == files.end()) {
-        LOGFAT("UpnpDevice::UpnpDevice: no description.xml found in xmlfiles"
-               << endl);
-        return;
-    } 
-
-    const string& description = it->second.content;
-
-    for (it = files.begin(); it != files.end(); it++) {
-        theVD->addFile("/", it->first, it->second.content, it->second.mimetype);
-    }
-
-    // Start up the web server for sending out description files
-    int ret;
-    if ((ret = m_lib->setupWebServer(description, &m_dvh)) != 0) {
-        LOGFAT("UpnpDevice: libupnp can't start service. Err " << ret << endl);
-    }
-
-    if ((ret = UpnpSendAdvertisement(m_dvh, expiretime)) != 0) {
-        LOGERR(m_lib->errAsString("UpnpDevice: UpnpSendAdvertisement", ret)
-               << endl);
-    }
-}
-
-UpnpDevice::~UpnpDevice()
-{
-    UpnpUnRegisterRootDevice(m_dvh);
-
-    PTMutexLocker lock(o_devices_lock);
-    unordered_map<std::string, UpnpDevice *>::iterator it = o_devices.find(m_deviceId);
-    if (it != o_devices.end())
-        o_devices.erase(it);
-}
-
-// Main libupnp callback: use the device id and call the right device
-int UpnpDevice::sCallBack(Upnp_EventType et, void* evp, void* tok)
-{
-    //LOGDEB("UpnpDevice::sCallBack" << endl);
-
-    string deviceid;
-    switch (et) {
-    case UPNP_CONTROL_ACTION_REQUEST:
-        deviceid = ((struct Upnp_Action_Request *)evp)->DevUDN;
-    break;
-
-    case UPNP_CONTROL_GET_VAR_REQUEST:
-        deviceid = ((struct Upnp_State_Var_Request *)evp)->DevUDN;
-    break;
-
-    case UPNP_EVENT_SUBSCRIPTION_REQUEST:
-        deviceid = ((struct  Upnp_Subscription_Request*)evp)->UDN;
-    break;
-
-    default:
-        LOGERR("UpnpDevice::sCallBack: unknown event " << et << endl);
-        return UPNP_E_INVALID_PARAM;
-    }
-    // LOGDEB("UpnpDevice::sCallBack: deviceid[" << deviceid << "]" << endl);
-
-    unordered_map<std::string, UpnpDevice *>::iterator it;
-    {
-        PTMutexLocker lock(o_devices_lock);
-
-        it = o_devices.find(deviceid);
-
-        if (it == o_devices.end()) {
-            LOGERR("UpnpDevice::sCallBack: Device not found: [" << 
-                   deviceid << "]" << endl);
-            return UPNP_E_INVALID_PARAM;
-        }
-    }
-
-    // LOGDEB("UpnpDevice::sCallBack: device found: [" << it->second 
-    // << "]" << endl);
-    return (it->second)->callBack(et, evp);
-}
-
-unordered_map<string, UpnpService*>::const_iterator UpnpDevice::findService(const string& serviceid)
-{
-    PTMutexLocker lock(m_lock);
-    auto servit = m_servicemap.find(serviceid);
-    if (servit == m_servicemap.end()) {
-        LOGERR("UpnpDevice: Bad serviceID: " << serviceid << endl);
-    }
-    return servit;
-}
-
-int UpnpDevice::callBack(Upnp_EventType et, void* evp)
-{
-    switch (et) {
-    case UPNP_CONTROL_ACTION_REQUEST:
-    {
-        struct Upnp_Action_Request *act = (struct Upnp_Action_Request *)evp;
-
-        LOGDEB("UPNP_CONTROL_ACTION_REQUEST: " << act->ActionName <<
-               ". Params: " << ixmlwPrintDoc(act->ActionRequest) << endl);
-
-        auto servit = findService(act->ServiceID);
-        if (servit == m_servicemap.end()) {
-            return UPNP_E_INVALID_PARAM;
-        }
-
-        SoapData dt;
-        {
-            PTMutexLocker lock(m_lock);
-            const string& servicetype = servit->second->getServiceType();
-            auto callit = m_calls.find(string(act->ActionName) + string(act->ServiceID));
-            if (callit == m_calls.end()) {
-                LOGINF("UpnpDevice: No such action: " << act->ActionName << endl);
-                return UPNP_E_INVALID_PARAM;
-            }
-
-            SoapArgs sc;
-            if (!decodeSoapBody(act->ActionName, act->ActionRequest, &sc)) {
-                LOGERR("Error decoding Action call arguments" << endl);
-                return UPNP_E_INVALID_PARAM;
-            }
-            dt.name = act->ActionName;
-            dt.serviceType = servicetype;
-
-            // Call the action routine
-            int ret = callit->second(sc, dt);
-            if (ret != UPNP_E_SUCCESS) {
-                LOGERR("UpnpDevice: Action failed: " << sc.name << endl);
-                return ret;
-            }
-        }
-
-        // Encode result data
-        act->ActionResult = buildSoapBody(dt);
-
-        //LOGDEB("Response data: " << ixmlwPrintDoc(act->ActionResult) << endl);
-
-        return UPNP_E_SUCCESS;
-    }
-    break;
-
-    case UPNP_CONTROL_GET_VAR_REQUEST:
-        // Note that the "Control: query for variable" action is
-        // deprecated (upnp arch v1), and we should never get these.
-    {
-        struct Upnp_State_Var_Request *act = 
-            (struct Upnp_State_Var_Request *)evp;
-        LOGDEB("UPNP_CONTROL_GET_VAR__REQUEST?: " << act->StateVarName << endl);
-    }
-    break;
-
-    case UPNP_EVENT_SUBSCRIPTION_REQUEST:
-    {
-        struct Upnp_Subscription_Request *act = 
-            (struct  Upnp_Subscription_Request*)evp;
-        LOGDEB("UPNP_EVENT_SUBSCRIPTION_REQUEST: " << act->ServiceId << endl);
-
-        auto servit = findService(act->ServiceId);
-        if (servit == m_servicemap.end()) {
-            return UPNP_E_INVALID_PARAM;
-        }
-
-        vector<string> names, values, qvalues;
-        {
-            PTMutexLocker lock(m_lock);
-            if (!servit->second->getEventData(true, names, values)) {
-                break;
-            }
-        }
-
-        vector<const char *> cnames, cvalues;
-        vectorstoargslists(names, values, qvalues, cnames, cvalues);
-        int ret = 
-            UpnpAcceptSubscription(m_dvh, act->UDN, act->ServiceId,
-                                   &cnames[0], &cvalues[0],
-                                   int(cnames.size()), act->Sid);
-        if (ret != UPNP_E_SUCCESS) {
-            LOGERR(m_lib->errAsString(
-                       "UpnpDevice::callBack: UpnpAcceptSubscription", ret) <<
-                   endl);
-        }
-
-        return ret;
-    }
-    break;
-
-    default:
-        LOGINF("UpnpDevice::callBack: unknown libupnp event type: " <<
-               LibUPnP::evTypeAsString(et).c_str() << endl);
-        return UPNP_E_INVALID_PARAM;
-    }
-    return UPNP_E_INVALID_PARAM;
-}
-
-void UpnpDevice::addService(UpnpService *serv, const std::string& serviceId)
-{
-    PTMutexLocker lock(m_lock);
-    m_servicemap[serviceId] = serv;
-    m_serviceids.push_back(serviceId);
-}
-
-void UpnpDevice::addActionMapping(const UpnpService* serv,
-                                  const std::string& actName,
-                                  soapfun fun)
-{
-    PTMutexLocker lock(m_lock);
-    // LOGDEB("UpnpDevice::addActionMapping:" << actName << endl);
-    m_calls[actName + serv->getServiceId()] = fun;
-}
-
-void UpnpDevice::notifyEvent(const string& serviceId,
-                             const vector<string>& names, 
-                             const vector<string>& values)
-{
-    LOGDEB1("UpnpDevice::notifyEvent " << serviceId << " " <<
-           (names.empty()?"Empty names??":names[0]) << endl);
-    if (names.empty())
-        return;
-    vector<const char *> cnames, cvalues;
-    vector<string> qvalues;
-    vectorstoargslists(names, values, qvalues, cnames, cvalues);
-
-    int ret = UpnpNotify(m_dvh, m_deviceId.c_str(), 
-                         serviceId.c_str(), &cnames[0], &cvalues[0],
-                         int(cnames.size()));
-    if (ret != UPNP_E_SUCCESS) {
-        LOGERR(m_lib->errAsString("UpnpDevice::notifyEvent", ret) << endl);
-    }
-}
-
-int timespec_diffms(const struct timespec& old, const struct timespec& recent)
-{
-    return (recent.tv_sec - old.tv_sec) * 1000 + 
-        (recent.tv_nsec - old.tv_nsec) / (1000 * 1000);
-}
-
-#ifndef CLOCK_REALTIME
-// Mac OS X for one does not have clock_gettime. Gettimeofday is more than
-// enough for our needs.
-#define CLOCK_REALTIME 0
-int clock_gettime(int /*clk_id*/, struct timespec* t) {
-    struct timeval now;
-    int rv = gettimeofday(&now, NULL);
-    if (rv) return rv;
-    t->tv_sec  = now.tv_sec;
-    t->tv_nsec = now.tv_usec * 1000;
-    return 0;
-}
-#endif // ! CLOCK_REALTIME
-
-// Loop on services, and poll each for changed data. Generate event
-// only if changed data exists. Every now and then, we generate an
-// artificial event with all the current state. This is normally run by the main thread.
-void UpnpDevice::eventloop()
-{
-    int count = 0;
-    const int loopwait_ms = 1000; // Polling the services every 1 S
-    const int nloopstofull = 10;  // Full state every 10 S
-    struct timespec wkuptime, earlytime;
-    bool didearly = false;
-
-    for (;;) {
-        clock_gettime(CLOCK_REALTIME, &wkuptime);
-
-        timespec_addnanos(&wkuptime, loopwait_ms * 1000 * 1000);
-
-        //LOGDEB("eventloop: now " << time(0) << " wkup at "<< 
-        //    wkuptime.tv_sec << " S " << wkuptime.tv_nsec << " ns" << endl);
-
-        PTMutexLocker lock(m_evlooplock);
-        int err = pthread_cond_timedwait(&m_evloopcond, lock.getMutex(), 
-                                         &wkuptime);
-        if (m_needExit) {
-            break;
-        } else if (err && err != ETIMEDOUT) {
-            LOGINF("UpnpDevice:eventloop: wait errno " << errno << endl);
-            break;
-        } else if (err == 0) {
-            // Early wakeup. Only does something if it did not already
-            // happen recently
-            if (didearly) {
-                int millis = timespec_diffms(earlytime, wkuptime);
-                if (millis < loopwait_ms) {
-                    // Do nothing. didearly stays true
-                    // LOGDEB("eventloop: early, previous too close "<<endl);
-                    continue;
-                } else {
-                    // had an early wakeup previously, but it was a
-                    // long time ago. Update state and wakeup
-                    // LOGDEB("eventloop: early, previous is old "<<endl);
-                    earlytime = wkuptime;
+#ifndef _EXPATMM_EXPATXMLPARSER_H
+#define _EXPATMM_EXPATXMLPARSER_H
+
+#include <string.h>
+#include <expat.h>
+
+namespace UPnPP {
+
+class ExpatXMLParser {
+public:
+
+    /* Create a new parser, using the default Chunk Size */
+    ExpatXMLParser(void)
+        {
+            init();
+        }
+
+    /* Create a new parser, using a user-supplied chunk size */
+    ExpatXMLParser(size_t chunk_size)
+        {
+            init(chunk_size);
+        }
+
+    /* Destructor that cleans up xml_buffer and parser */
+    virtual ~ExpatXMLParser(void)
+        {
+            valid_parser = false;
+            if(expat_parser != NULL) {
+                XML_ParserFree(expat_parser);
+                expat_parser = NULL;
+            }
+            if(xml_buffer != NULL) {
+                delete xml_buffer;
+                xml_buffer = NULL;
+            }
+        }
+
+    /*
+      Generic Parser. Most derivations will simply call this, rather
+      than implement their own. This will loop, processing XML data
+      and calling the necessary handler code until an error is encountered.
+    */
+    virtual bool Parse(void)
+        {
+            /* Ensure that the parser is ready */
+            if(!Ready())
+                return false;
+
+            ssize_t bytes_read;
+            /* Loop, reading the XML source block by block */
+            while((bytes_read = read_block()) >= 0) {
+                if(bytes_read > 0) {
+                    XML_Status local_status =
+                        XML_Parse(expat_parser, getReadBuffer(), bytes_read,
+                                  XML_FALSE);
+
+                    if(local_status != XML_STATUS_OK) {
+                        status = local_status;
+                        last_error = XML_GetErrorCode(expat_parser);
+                        break;
+                    }
+
+                    /* Break on successful "short read", in event of EOF */
+                    if(getLastError() == XML_ERROR_FINISHED)
+                        break;
                 }
-            } else {
-                // Early wakeup, previous one was normal. Remember.
-                // LOGDEB("eventloop: early, no previous" << endl);
-                didearly = true;
-                earlytime = wkuptime;
-            }
-        } else {
-            // Normal wakeup
-            // LOGDEB("eventloop: normal wakeup" << endl);
-            didearly = false;
-        }
-
-        count++;
-        bool all = count && ((count % nloopstofull) == 0);
-        //LOGDEB("UpnpDevice::eventloop count "<<count<<" all "<<all<<endl);
-
-        // We can't lock m_lock around the loop because we don't want
-        // to hold id when calling notifyEvent() (which calls
-        // libupnp). This means that we should have a separate lock
-        // for the services arrays. This would only be useful during
-        // startup, while we add services, but the event loop is the
-        // last call the main program will make after adding the
-        // services, so locking does not seem necessary
-        for (auto it = m_serviceids.begin(); it != m_serviceids.end(); it++) {
-            vector<string> names, values;
-            {
-                PTMutexLocker lock(m_lock);
-                UpnpService* serv = m_servicemap[*it];
-                if (!serv->getEventData(all, names, values) || names.empty()) {
-                    continue;
-                }
-            }
-            notifyEvent(*it, names, values);
-        }
-    }
-}
-
-// Can't take the loop lock here. We're called from the service and
-// hold the device lock. The locks would be taken in opposite order, 
-// causing a potential deadlock:
-//  - device action takes device lock
-//  - loop wakes up, takes loop lock
-//  - blocks on device lock before calling getevent
-//  - device calls loopwakeup which blocks on loop lock
-// -> deadlock
-void UpnpDevice::loopWakeup()
-{
-    pthread_cond_broadcast(&m_evloopcond);
-}
-
-void UpnpDevice::shouldExit()
-{
-    m_needExit = true;
-    pthread_cond_broadcast(&m_evloopcond);
-}
-
-}// End namespace UPnPProvider
+            }
+
+            /* Finalize the parser */
+            if((getStatus() == XML_STATUS_OK) ||
+               (getLastError() == XML_ERROR_FINISHED)) {
+                XML_Parse(expat_parser, getBuffer(), 0, XML_TRUE);
+                return true;
+            }
+
+            /* Return false in the event of an error. The parser is
+               not finalized on error. */
+            return false;
+        }
+
+    /* Expose status, error, and control codes to users */
+    virtual bool Ready(void) { return valid_parser; };
+    virtual XML_Error getLastError(void) { return last_error; };
+    virtual XML_Status getStatus(void) { return status; };
+
+protected:
+    virtual XML_Char *getBuffer(void) { return xml_buffer; };
+    virtual const char *getReadBuffer(void) { return xml_buffer; };
+    virtual size_t getBlockSize(void) { return xml_buffer_size; };
+
+    /* Read XML data.
+     *
+     * Override this to implement your container-specific parser.
+     *
+     * You must:
+     * put new XML data into xml_buffer
+     * set status
+     * set last_error
+     * return the amount of XML_Char's written to xml_buffer
+     *
+     * on error, return < 0. The contents of xml_buffer will be
+     * thrown away in this event, so it is the derived class's
+     * responsibility to reseek the "data cursor" to re-get any
+     * data in the buffer on an error condition.
+     *
+     * Use setReadiness, setStatus, and setLastError to handle
+     * error, status, and control events and codes.
+     *
+     * The default implementation returns "no elements" if it is
+     * ever called. and should be overridden by the derived class.
+     *
+     * Note that, as the actual parser only uses
+     * getBuffer()/getBlockSize()/read_block() (no direct access
+     * to the buffer), you are free to use an entirely different
+     * I/O mechanism, like what does the inputRefXMLParser below.
+     */
+    virtual ssize_t read_block(void)
+        {
+            last_error = XML_ERROR_NO_ELEMENTS;
+            status = XML_STATUS_ERROR;
+            return -1;
+        }
+
+    virtual void setReadiness(bool ready)
+        {
+            valid_parser = ready;
+        }
+    virtual void setStatus(XML_Status new_status)
+        {
+            status = new_status;
+        }
+    virtual void setLastError(XML_Error new_last_error)
+        {
+            last_error = new_last_error;
+        };
+
+    /* Methods to be overriden */
+    virtual void StartElement(const XML_Char *,
+                              const XML_Char **) {}
+    virtual void EndElement(const XML_Char *) {}
+    virtual void CharacterData(const XML_Char *, int) {}
+    virtual void ProcessingInstruction(const XML_Char *,
+                                       const XML_Char *) {}
+    virtual void CommentData(const XML_Char *) {}
+    virtual void DefaultHandler(const XML_Char *, int) {}
+    virtual void CDataStart(void) {}
+    virtual void CDataEnd(void) {}
+
+    /* The handle for the parser (expat) */
+    XML_Parser expat_parser;
+
+private:
+
+    /* Temporary buffer where data is streamed in */
+    XML_Char *xml_buffer;
+    size_t xml_buffer_size;
+
+    /* Tells if the parser is ready to accept data */
+    bool valid_parser;
+
+    /* Status and Error codes in the event of unforseen events */
+    XML_Status status;
+    XML_Error last_error;
+
+    /* Expat callbacks.
+     * The expatmm protocol is to pass (this) as the userData argument
+     * in the XML_Parser structure. These static methods will convert
+     * handlers into upcalls to the instantiated class's virtual members
+     * to do the actual handling work. */
+    static void _element_start_handler(void *userData, const XML_Char *name,
+                                       const XML_Char **atts)
+        {
+            ExpatXMLParser *me = (ExpatXMLParser*)userData;
+            if(me != NULL) me->StartElement(name, atts);
+        }
+    static void _element_end_handler(void *userData, const XML_Char *name)
+        {
+            ExpatXMLParser *me = (ExpatXMLParser*)userData;
+            if(me != NULL) me->EndElement(name);
+        }
+    static void _character_data_handler(void *userData,
+                                        const XML_Char *s, int len)
+        {
+            ExpatXMLParser *me = (ExpatXMLParser*)userData;
+            if(me != NULL) me->CharacterData(s, len);
+        }
+    static void _processing_instr_handler(void *userData,
+                                          const XML_Char *target,
+                                          const XML_Char *data)
+        {
+            ExpatXMLParser *me = (ExpatXMLParser*)userData;
+            if(me != NULL) me->ProcessingInstruction(target, data);
+        }
+    static void _comment_handler(void *userData, const XML_Char *data)
+        {
+            ExpatXMLParser *me = (ExpatXMLParser*)userData;
+            if(me != NULL) me->CommentData(data);
+        }
+    static void _default_handler(void *userData, const XML_Char *s, int len)
+        {
+            ExpatXMLParser *me = (ExpatXMLParser*)userData;
+            if(me != NULL) me->DefaultHandler(s, len);
+        }
+    static void _cdata_start_handler(void *userData)
+        {
+            ExpatXMLParser *me = (ExpatXMLParser*)userData;
+            if(me != NULL) me->CDataStart();
+        }
+    static void _cdata_end_handler(void *userData)
+        {
+            ExpatXMLParser *me = (ExpatXMLParser*)userData;
+            if(me != NULL) me->CDataEnd();
+        }
+    /* Register our static handlers with the Expat events. */
+    void register_default_handlers()
+        {
+            XML_SetElementHandler(expat_parser, &_element_start_handler,
+                                  &_element_end_handler);
+            XML_SetCharacterDataHandler(expat_parser, &_character_data_handler);
+            XML_SetProcessingInstructionHandler(expat_parser,
+                                                &_processing_instr_handler);
+            XML_SetCommentHandler(expat_parser, &_comment_handler);
+            XML_SetCdataSectionHandler(expat_parser, &_cdata_start_handler,
+                                       &_cdata_end_handler);
+            XML_SetDefaultHandler(expat_parser, &_default_handler);
+        }
+    /* Constructor common code */
+    void init(size_t chunk_size = 0)
+        {
+            valid_parser = false;
+            expat_parser = NULL;
+            xml_buffer_size = chunk_size ? chunk_size : 10240;
+            xml_buffer = new XML_Char[xml_buffer_size];
+            if(xml_buffer == NULL)
+                return;
+            expat_parser = XML_ParserCreate(NULL);
+
+            if(expat_parser == NULL) {
+                delete xml_buffer;
+                xml_buffer = NULL;
+                return;
+            }
+            status = XML_STATUS_OK;
+            last_error = XML_ERROR_NONE;
+
+            memset(xml_buffer, 0, chunk_size * sizeof(XML_Char));
+
+            /* Set the "ready" flag on this parser */
+            valid_parser = true;
+            XML_SetUserData(expat_parser, (void*)this);
+            register_default_handlers();
+        }
+};
+
+/** A specialization of ExpatXMLParser that does not copy its input */
+class inputRefXMLParser : public ExpatXMLParser {
+public:
+    // Beware: we only use a ref to input to minimize copying. This means
+    // that storage for the input parameter must persist until you are done
+    // with the parser object !
+    inputRefXMLParser(const std::string& input)
+        : ExpatXMLParser(1), // Have to allocate a small buf even if not used.
+          m_input(input)
+        {}
+
+protected:
+    ssize_t read_block(void)
+        {
+            if (getLastError() == XML_ERROR_FINISHED) {
+                setStatus(XML_STATUS_OK);
+                return -1;
+            }
+            setLastError(XML_ERROR_FINISHED);
+            return m_input.size();
+        }
+    const char *getReadBuffer()
+        {
+            return m_input.c_str();
+        }
+    virtual size_t getBlockSize(void)
+        {
+            return m_input.size();
+        }
+protected:
+    const std::string& m_input;
+};
+
+}; // End namespace 
+
+#endif /* _EXPATMM_EXPATXMLPARSER_H */
src/device/device.hxx to libupnpp/device/vdir.cxx
--- a/libupnpp/device/device.hxx
+++ b/libupnpp/device/vdir.cxx
@@ -1,189 +1,204 @@
-/* Copyright (C) 2014 J.F.Dockes
- *	 This program is free software; you can redistribute it and/or modify
- *	 it under the terms of the GNU General Public License as published by
- *	 the Free Software Foundation; either version 2 of the License, or
- *	 (at your option) any later version.
+/* Copyright (C) 2013 J.F.Dockes
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
  *
- *	 This program is distributed in the hope that it will be useful,
- *	 but WITHOUT ANY WARRANTY; without even the implied warranty of
- *	 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *	 GNU General Public License for more details.
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
  *
- *	 You should have received a copy of the GNU General Public License
- *	 along with this program; if not, write to the
- *	 Free Software Foundation, Inc.,
- *	 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the
+ *   Free Software Foundation, Inc.,
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
-#ifndef _DEVICE_H_X_INCLUDED_
-#define _DEVICE_H_X_INCLUDED_
-
-#include <pthread.h>                    // for pthread_cond_t
-#include <upnp/upnp.h>                  // for Upnp_EventType, etc
-
-#include <functional>                   // for function
-#include <string>                       // for string
-#include <unordered_map>                // for unordered_map, etc
-#include <vector>                       // for vector
-
-#include "libupnpp/ptmutex.hxx"         // for PTMutexInit
-#include "libupnpp/soaphelp.hxx"        // for SoapArgs, SoapData
-
-namespace UPnPP { class LibUPnP; }
-namespace UPnPProvider { class UpnpService; }
+#include "config.h"
+
+#include "vdir.hxx"
+
+#include <string.h>                     // for memcpy
+#include <sys/types.h>                  // for off_t
+#include <upnp/ixml.h>                  // for ixmlCloneDOMString
+#include <upnp/upnp.h>                  // for File_Info, etc
+
+#include <iostream>                     // for endl, basic_ostream, etc
+#include <utility>                      // for pair
+
+#include "libupnpp/log.hxx"             // for LOGERR
+#include "libupnpp/upnpp_p.hxx"         // for path_getfather, etc
+
+using namespace std;
+using namespace UPnPP;
 
 namespace UPnPProvider {
 
-typedef std::function<int (const UPnPP::SoapArgs&, UPnPP::SoapData&)> soapfun;
-
-// Definition of a virtual directory entry: data and mime type
-struct VDirContent {
-    VDirContent(const std::string& ct, const std::string& mt)
-        : content(ct), mimetype(mt) {}
-    std::string content;
-    std::string mimetype;
+static VirtualDir *theDir;
+
+struct Handle {
+    Handle(VirtualDir::FileEnt *e)
+        : entry(e), offset(0) {
+    }
+    VirtualDir::FileEnt *entry;
+    size_t offset;
 };
 
-/** Define a virtual interface to link libupnp operations to a device 
- * implementation 
- */
-class UpnpDevice {
-public:
-    /** Construct device object
-     * @param deviceId uuid for device: "uuid:UUIDvalue"
-     * @param files list of name/content pairs to be added to the
-     *   virtual directory root. The file names must match the SCDPURL
-     *   values for the services in the description.xml document. Note that 
-     *   this will have to be changed if we ever want to really
-     *   support multiple devices (will need to use subdirectories or
-     *   find another way to avoid name conflicts).
-     */
-    UpnpDevice(const std::string& deviceId, 
-               const std::unordered_map<std::string, VDirContent>& files);
-    ~UpnpDevice();
-
-    void addService(UpnpService *, const std::string& serviceId);
-
-    /**
-     * Add mapping from service+action-name to handler function.
-     */
-    void addActionMapping(const UpnpService*, 
-                          const std::string& actName, soapfun);
-
-    /** 
-     * Main routine. To be called by main() when done with initialization. 
-     *
-     * This loop mostly polls getEventData and generates an UPnP event if
-     * there is anything to broadcast. The UPnP action calls happen in
-     * other threads with which we synchronize, currently using a global lock.
-     */
-    void eventloop();
-
-    /** 
-     * To be called from a service action callback to wake up the
-     * event loop early if something needs to be broadcast without
-     * waiting for the normal delay.
-     *
-     * Will only do something if the previous event is not too recent.
-     */
-    void loopWakeup(); // To trigger an early event
-
-    /**
-     * To be called to get the event loop to return
-     */
-    void shouldExit();
-
-    /** Check status */
-    bool ok() {return m_lib != 0;}
-
-private:
-    const std::string& serviceType(const std::string& serviceId);
-            
-    UPnPP::LibUPnP *m_lib;
-    std::string m_deviceId;
-    // We keep the services in a map for easy access from id and in a
-    // vector for ordered walking while fetching status. Order is
-    // determine by addService() call sequence.
-    std::unordered_map<std::string, UpnpService*> m_servicemap;
-    std::vector<std::string> m_serviceids;
-    std::unordered_map<std::string, soapfun> m_calls;
-    std::unordered_map<std::string, UpnpService*>::const_iterator findService(const std::string& serviceid);
-
-    bool m_needExit;
-    /* My device handle */
-    UpnpDevice_Handle m_dvh;
-
-    /* Lock for device operations. Held during a service callback 
-       Must not be held when using m_dvh to call into libupnp */
-    UPnPP::PTMutexInit m_lock;
-
-    pthread_cond_t m_evloopcond;
-    UPnPP::PTMutexInit m_evlooplock;
-
-    /* Gets called when something needs doing */
-    int callBack(Upnp_EventType et, void* evp);
-
-    /** 
-     * Generate event.
-     *
-     * Called by the device event loop, which polls the services.
-     * Use loopwakeup() to expedite things.
-     */
-    void notifyEvent(const std::string& serviceId,
-                     const std::vector<std::string>& names, 
-                     const std::vector<std::string>& values);
-
-
-    /** Static array of devices for dispatching */
-    static std::unordered_map<std::string, UpnpDevice *> o_devices;
-
-    /* Static callback for libupnp. This looks up the appropriate
-     * device using the device ID (UDN), the calls its callback
-     * method */
-    static int sCallBack(Upnp_EventType et, void* evp, void*);
+static int vdclose(UpnpWebFileHandle fileHnd)
+{
+    Handle *h = (Handle*)fileHnd;
+    delete h;
+    return 0;
+}
+
+static VirtualDir::FileEnt *vdgetentry(const char *pathname)
+{
+    //LOGDEB("vdgetentry: [" << pathname << "]" << endl);
+    VirtualDir *thedir = VirtualDir::getVirtualDir();
+    if (thedir == 0) {
+        return 0;
+    }
+
+    string dir = path_getfather(pathname);
+    string fn = path_getsimple(pathname);
+
+    return theDir->getFile(dir, fn);
+}
+
+static int vdgetinfo(const char *fn, struct File_Info* info)
+{
+    //LOGDEB("vdgetinfo: [" << fn << "] off_t " << sizeof(off_t) <<
+    // " time_t " << sizeof(time_t) << endl);
+    VirtualDir::FileEnt *entry = vdgetentry(fn);
+    if (entry == 0) {
+        LOGERR("vdgetinfo: no entry for " << fn << endl);
+        return -1;
+    }
+
+    info->file_length = entry->content.size();
+    info->last_modified = entry->mtime;
+    info->is_directory = 0;
+    info->is_readable = 1;
+    info->content_type = ixmlCloneDOMString(entry->mimetype.c_str());
+
+    return 0;
+}
+
+static UpnpWebFileHandle vdopen(const char* fn, enum UpnpOpenFileMode Mode)
+{
+    //LOGDEB("vdopen: " << fn << endl);
+    VirtualDir::FileEnt *entry = vdgetentry(fn);
+    if (entry == 0) {
+        LOGERR("vdopen: no entry for " << fn << endl);
+        return NULL;
+    }
+    return new Handle(entry);
+}
+
+static int vdread(UpnpWebFileHandle fileHnd, char* buf, size_t buflen)
+{
+    // LOGDEB("vdread: " << endl);
+    if (buflen == 0) {
+        return 0;
+    }
+    Handle *h = (Handle *)fileHnd;
+    if (h->offset >= h->entry->content.size()) {
+        return 0;
+    }
+    size_t toread = buflen > h->entry->content.size() - h->offset ?
+                    h->entry->content.size() - h->offset : buflen;
+    memcpy(buf, h->entry->content.c_str() + h->offset, toread);
+    h->offset += toread;
+    return toread;
+}
+
+static int vdseek(UpnpWebFileHandle fileHnd, off_t offset, int origin)
+{
+    // LOGDEB("vdseek: " << endl);
+    Handle *h = (Handle *)fileHnd;
+    if (origin == 0) {
+        h->offset = offset;
+    } else if (origin == 1) {
+        h->offset += offset;
+    } else if (origin == 2) {
+        h->offset = h->entry->content.size() + offset;
+    } else {
+        return -1;
+    }
+    return offset;
+}
+
+static int vdwrite(UpnpWebFileHandle fileHnd, char* buf, size_t buflen)
+{
+    LOGERR("vdwrite" << endl);
+    return -1;
+}
+
+static struct UpnpVirtualDirCallbacks myvdcalls = {
+    vdgetinfo, vdopen, vdread, vdwrite, vdseek, vdclose
 };
 
-/**
- * Upnp service implementation class. This does not do much useful beyond
- * encapsulating the service actions and event callback. In most cases, the
- * services will need full access to the device state anyway.
- */
-class UpnpService {
-public:
-    UpnpService(const std::string& stp,const std::string& sid, UpnpDevice *dev) 
-        : m_serviceType(stp), m_serviceId(sid)
-        {
-            dev->addService(this, sid);
+VirtualDir *VirtualDir::getVirtualDir()
+{
+    if (theDir == 0) {
+        theDir = new VirtualDir();
+        if (UpnpSetVirtualDirCallbacks(&myvdcalls) != UPNP_E_SUCCESS) {
+            LOGERR("SetVirtualDirCallbacks failed" << endl);
+            delete theDir;
+            theDir = 0;
+            return 0;
         }
-    virtual ~UpnpService() {}
-
-    /** 
-     * Poll to retrieve evented data changed since last call.
-     *
-     * To be implemented by the derived class.
-     * Called by the library when a control point subscribes, to
-     * retrieve eventable data. 
-     * Return name/value pairs for changed variables in the data arrays.
-     */
-    virtual bool getEventData(bool all, std::vector<std::string>& names, 
-                              std::vector<std::string>& values) 
-        {
-            return true;
-        }
-
-    virtual const std::string& getServiceType() const
-        {
-            return m_serviceType;
-        }
-    virtual const std::string& getServiceId() const
-        {
-            return m_serviceId;
-        }
-
-protected:
-    const std::string m_serviceType;
-    const std::string m_serviceId;
-};
-
-} // End namespace UPnPProvider
-
-#endif /* _DEVICE_H_X_INCLUDED_ */
+    }
+    return theDir;
+}
+
+bool VirtualDir::addFile(const string& _path, const string& name,
+                         const string& content, const string& mimetype)
+{
+    string path(_path);
+    if (path.empty() || path[path.size() - 1] != '/') {
+        path += '/';
+    }
+    //LOGDEB("VirtualDir::addFile: path " << path << " name " << name << endl);
+
+    if (m_dirs.find(path) == m_dirs.end()) {
+        m_dirs[path] = unordered_map<string, VirtualDir::FileEnt>();
+        UpnpAddVirtualDir(path.c_str());
+    }
+
+    VirtualDir::FileEnt entry;
+    entry.mtime = time(0);
+    entry.mimetype = mimetype;
+    entry.content = content;
+    m_dirs[path][name] = entry;
+    // LOGDEB("VirtualDir::addFile: added entry for dir " <<
+    // path << " name " << name << endl);
+    return true;
+}
+
+VirtualDir::FileEnt *VirtualDir::getFile(const string& _path,
+        const string& name)
+{
+    string path(_path);
+    if (path.empty() || path[path.size() - 1] != '/') {
+        path += '/';
+    }
+
+    // LOGDEB("VirtualDir::getFile: path " << path << " name " << name << endl);
+
+    unordered_map<string, unordered_map<string, VirtualDir::FileEnt> >::iterator dir =
+        m_dirs.find(path);
+    if (dir == m_dirs.end()) {
+        LOGERR("VirtualDir::getFile: no dir: " << path << endl);
+        return 0;
+    }
+    unordered_map<string, FileEnt>::iterator f = dir->second.find(name);
+    if (f == dir->second.end()) {
+        LOGERR("VirtualDir::getFile: no file: " << path << endl);
+        return 0;
+    }
+
+    return &(f->second);
+}
+
+}
src/device/vdir.cxx to libupnpp/soaphelp.cxx
--- a/libupnpp/device/vdir.cxx
+++ b/libupnpp/soaphelp.cxx
@@ -1,204 +1,303 @@
-/* Copyright (C) 2013 J.F.Dockes
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 2 of the License, or
- *   (at your option) any later version.
+/* Copyright (C) 2014 J.F.Dockes
+ *	 This program is free software; you can redistribute it and/or modify
+ *	 it under the terms of the GNU General Public License as published by
+ *	 the Free Software Foundation; either version 2 of the License, or
+ *	 (at your option) any later version.
  *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
+ *	 This program is distributed in the hope that it will be useful,
+ *	 but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	 GNU General Public License for more details.
  *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the
- *   Free Software Foundation, Inc.,
- *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *	 You should have received a copy of the GNU General Public License
+ *	 along with this program; if not, write to the
+ *	 Free Software Foundation, Inc.,
+ *	 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
 #include "config.h"
 
-#include "vdir.hxx"
-
-#include <string.h>                     // for memcpy
-#include <sys/types.h>                  // for off_t
-#include <upnp/ixml.h>                  // for ixmlCloneDOMString
-#include <upnp/upnp.h>                  // for File_Info, etc
-
-#include <iostream>                     // for endl, basic_ostream, etc
-#include <utility>                      // for pair
-
-#include "libupnpp/log.hxx"             // for LOGERR
-#include "libupnpp/upnpp_p.hxx"         // for path_getfather, etc
+#include "libupnpp/soaphelp.hxx"
+
+#include <stdio.h>                      // for sprintf
+#include <stdlib.h>                     // for atoi
+
+#include <iostream>                     // for operator<<, endl, etc
+
+#include "libupnpp/log.hxx"             // for LOGDEB, LOGERR, LOGDEB1
+#include "libupnpp/upnpp_p.hxx"         // for stringToBool
 
 using namespace std;
-using namespace UPnPP;
-
-namespace UPnPProvider {
-
-static VirtualDir *theDir;
-
-struct Handle {
-    Handle(VirtualDir::FileEnt *e)
-        : entry(e), offset(0) {
-    }
-    VirtualDir::FileEnt *entry;
-    size_t offset;
-};
-
-static int vdclose(UpnpWebFileHandle fileHnd)
-{
-    Handle *h = (Handle*)fileHnd;
-    delete h;
-    return 0;
-}
-
-static VirtualDir::FileEnt *vdgetentry(const char *pathname)
-{
-    //LOGDEB("vdgetentry: [" << pathname << "]" << endl);
-    VirtualDir *thedir = VirtualDir::getVirtualDir();
-    if (thedir == 0) {
+
+namespace UPnPP {
+
+/* Example Soap XML doc passed by libupnp is like: 
+   <ns0:SetMute>
+     <InstanceID>0</InstanceID>
+     <Channel>Master</Channel>
+     <DesiredMute>False</DesiredMute>
+   </ns0:SetMute>
+   
+   As the top node name is qualified by a namespace, it's easier to just use 
+   action name passed in the libupnp action callback.
+   
+   This is used both for decoding action requests in the device and responses
+   in the control point side
+*/
+bool decodeSoapBody(const char *callnm, IXML_Document *actReq, 
+                    SoapDecodeOutput *res)
+{
+    bool ret = false;
+    IXML_NodeList* nl = 0;
+    IXML_Node* topNode = 
+        ixmlNode_getFirstChild((IXML_Node *)actReq);
+    if (topNode == 0) {
+        LOGERR("decodeSoap: Empty Action request (no topNode) ??" << endl);
+        return false;
+    }
+    //LOGDEB("decodeSoap: top node name: " << ixmlNode_getNodeName(topNode) 
+    //       << endl);
+
+    nl = ixmlNode_getChildNodes(topNode);
+    if (nl == 0) {
+        // Ok actually, there are no args
+        return true;
+    }
+    //LOGDEB("decodeSoap: childnodes list length: " << ixmlNodeList_length(nl)
+    // << endl);
+
+    for (unsigned long i = 0; i <  ixmlNodeList_length(nl); i++) {
+        IXML_Node *cld = ixmlNodeList_item(nl, i);
+        if (cld == 0) {
+            LOGDEB1("decodeSoap: got null node  from nodelist at index " <<
+                   i << " ??" << endl);
+            // Seems to happen with empty arg list?? This looks like a bug, 
+            // should we not get an empty node instead?
+            if (i == 0) {
+                ret = true;
+            }
+            goto out;
+        }
+        const char *name = ixmlNode_getNodeName(cld);
+        if (name == 0) {
+            DOMString pnode = ixmlPrintNode(cld);
+            LOGDEB("decodeSoap: got null name ??:" << pnode << endl);
+            ixmlFreeDOMString(pnode);
+            goto out;
+        }
+        IXML_Node *txtnode = ixmlNode_getFirstChild(cld);
+        const char *value = "";
+        if (txtnode != 0) {
+            value = ixmlNode_getNodeValue(txtnode);
+        }
+        // Can we get an empty value here ?
+        if (value == 0)
+            value = "";
+        res->args[name] = value;
+    }
+    res->name = callnm;
+    ret = true;
+out:
+    if (nl)
+        ixmlNodeList_free(nl);
+    return ret;
+}
+
+bool SoapDecodeOutput::getBool(const char *nm, bool *value) const
+{
+    map<string, string>::const_iterator it = args.find(nm);
+    if (it == args.end() || it->second.empty()) {
+        return false;
+    }
+    return stringToBool(it->second, value);
+}
+
+bool SoapDecodeOutput::getInt(const char *nm, int *value) const
+{
+    map<string, string>::const_iterator it = args.find(nm);
+    if (it == args.end() || it->second.empty()) {
+        return false;
+    }
+    *value = atoi(it->second.c_str());
+    return true;
+}
+
+bool SoapDecodeOutput::getString(const char *nm, string *value) const
+{
+    map<string, string>::const_iterator it = args.find(nm);
+    if (it == args.end()) {
+        return false;
+    }
+    *value = it->second;
+    return true;
+}
+
+namespace SoapHelp {
+string xmlQuote(const string& in)
+{
+    string out;
+    for (unsigned int i = 0; i < in.size(); i++) {
+        switch(in[i]) {
+        case '"': out += "&quot;";break;
+        case '&': out += "&amp;";break;
+        case '<': out += "&lt;";break;
+        case '>': out += "&gt;";break;
+        case '\'': out += "&apos;";break;
+        default: out += in[i];
+        }
+    }
+    return out;
+}
+
+string xmlUnquote(const string& in)
+{
+    string out;
+    for (unsigned int i = 0; i < in.size(); i++) {
+        if (in[i] == '&') {
+            unsigned int j;
+            for (j = i; j < in.size(); j++) {
+                if (in[j] == ';')
+                    break;
+            }
+            if (in[j] != ';') {
+                out += in.substr(i);
+                return out;
+            }
+            string entname = in.substr(i+1, j-i-1);
+            //cerr << "entname [" << entname << "]" << endl;
+            if (!entname.compare("quot")) {
+                out += '"';
+            } else if (!entname.compare("amp")) {
+                out += '&';
+            } else if (!entname.compare("lt")) {
+                out += '<';
+            } else if (!entname.compare("gt")) {
+                out += '>';
+            } else if (!entname.compare("apos")) {
+                out += '\'';
+            } else {
+                out += in.substr(i, j-i+1);
+            }
+            i = j;
+        } else {
+            out += in[i];
+        }
+    }
+    return out;
+}
+
+// Yes inefficient. whatever...
+string i2s(int val)
+{
+    char cbuf[30];
+    sprintf(cbuf, "%d", val);
+    return string(cbuf);
+}
+
+}
+
+IXML_Document *buildSoapBody(const SoapEncodeInput& data, bool isResponse)
+{
+    IXML_Document *doc = ixmlDocument_createDocument();
+    if (doc == 0) {
+        cerr << "buildSoapResponse: out of memory" << endl;
         return 0;
     }
-
-    string dir = path_getfather(pathname);
-    string fn = path_getsimple(pathname);
-
-    return theDir->getFile(dir, fn);
-}
-
-static int vdgetinfo(const char *fn, struct File_Info* info)
-{
-    //LOGDEB("vdgetinfo: [" << fn << "] off_t " << sizeof(off_t) <<
-    // " time_t " << sizeof(time_t) << endl);
-    VirtualDir::FileEnt *entry = vdgetentry(fn);
-    if (entry == 0) {
-        LOGERR("vdgetinfo: no entry for " << fn << endl);
-        return -1;
-    }
-
-    info->file_length = entry->content.size();
-    info->last_modified = entry->mtime;
-    info->is_directory = 0;
-    info->is_readable = 1;
-    info->content_type = ixmlCloneDOMString(entry->mimetype.c_str());
-
-    return 0;
-}
-
-static UpnpWebFileHandle vdopen(const char* fn, enum UpnpOpenFileMode Mode)
-{
-    //LOGDEB("vdopen: " << fn << endl);
-    VirtualDir::FileEnt *entry = vdgetentry(fn);
-    if (entry == 0) {
-        LOGERR("vdopen: no entry for " << fn << endl);
-        return NULL;
-    }
-    return new Handle(entry);
-}
-
-static int vdread(UpnpWebFileHandle fileHnd, char* buf, size_t buflen)
-{
-    // LOGDEB("vdread: " << endl);
-    if (buflen == 0) {
-        return 0;
-    }
-    Handle *h = (Handle *)fileHnd;
-    if (h->offset >= h->entry->content.size()) {
-        return 0;
-    }
-    size_t toread = buflen > h->entry->content.size() - h->offset ?
-                    h->entry->content.size() - h->offset : buflen;
-    memcpy(buf, h->entry->content.c_str() + h->offset, toread);
-    h->offset += toread;
-    return toread;
-}
-
-static int vdseek(UpnpWebFileHandle fileHnd, off_t offset, int origin)
-{
-    // LOGDEB("vdseek: " << endl);
-    Handle *h = (Handle *)fileHnd;
-    if (origin == 0) {
-        h->offset = offset;
-    } else if (origin == 1) {
-        h->offset += offset;
-    } else if (origin == 2) {
-        h->offset = h->entry->content.size() + offset;
-    } else {
-        return -1;
-    }
-    return offset;
-}
-
-static int vdwrite(UpnpWebFileHandle fileHnd, char* buf, size_t buflen)
-{
-    LOGERR("vdwrite" << endl);
-    return -1;
-}
-
-static struct UpnpVirtualDirCallbacks myvdcalls = {
-    vdgetinfo, vdopen, vdread, vdwrite, vdseek, vdclose
-};
-
-VirtualDir *VirtualDir::getVirtualDir()
-{
-    if (theDir == 0) {
-        theDir = new VirtualDir();
-        if (UpnpSetVirtualDirCallbacks(&myvdcalls) != UPNP_E_SUCCESS) {
-            LOGERR("SetVirtualDirCallbacks failed" << endl);
-            delete theDir;
-            theDir = 0;
-            return 0;
-        }
-    }
-    return theDir;
-}
-
-bool VirtualDir::addFile(const string& _path, const string& name,
-                         const string& content, const string& mimetype)
-{
-    string path(_path);
-    if (path.empty() || path[path.size() - 1] != '/') {
-        path += '/';
-    }
-    //LOGDEB("VirtualDir::addFile: path " << path << " name " << name << endl);
-
-    if (m_dirs.find(path) == m_dirs.end()) {
-        m_dirs[path] = unordered_map<string, VirtualDir::FileEnt>();
-        UpnpAddVirtualDir(path.c_str());
-    }
-
-    VirtualDir::FileEnt entry;
-    entry.mtime = time(0);
-    entry.mimetype = mimetype;
-    entry.content = content;
-    m_dirs[path][name] = entry;
-    // LOGDEB("VirtualDir::addFile: added entry for dir " <<
-    // path << " name " << name << endl);
-    return true;
-}
-
-VirtualDir::FileEnt *VirtualDir::getFile(const string& _path,
-        const string& name)
-{
-    string path(_path);
-    if (path.empty() || path[path.size() - 1] != '/') {
-        path += '/';
-    }
-
-    // LOGDEB("VirtualDir::getFile: path " << path << " name " << name << endl);
-
-    unordered_map<string, unordered_map<string, VirtualDir::FileEnt> >::iterator dir =
-        m_dirs.find(path);
-    if (dir == m_dirs.end()) {
-        LOGERR("VirtualDir::getFile: no dir: " << path << endl);
-        return 0;
-    }
-    unordered_map<string, FileEnt>::iterator f = dir->second.find(name);
-    if (f == dir->second.end()) {
-        LOGERR("VirtualDir::getFile: no file: " << path << endl);
-        return 0;
-    }
-
-    return &(f->second);
-}
-
-}
+    string topname = string("u:") + data.name;
+    if (isResponse)
+        topname += "Response";
+
+    IXML_Element *top =  
+        ixmlDocument_createElementNS(doc, data.serviceType.c_str(), 
+                                     topname.c_str());
+    ixmlElement_setAttribute(top, "xmlns:u", data.serviceType.c_str());
+
+    for (unsigned i = 0; i < data.data.size(); i++) {
+        IXML_Element *elt = 
+            ixmlDocument_createElement(doc, data.data[i].first.c_str());
+        IXML_Node* textnode = 
+            ixmlDocument_createTextNode(doc, data.data[i].second.c_str());
+        ixmlNode_appendChild((IXML_Node*)elt,(IXML_Node*)textnode);
+        ixmlNode_appendChild((IXML_Node*)top,(IXML_Node*)elt);
+    }
+
+    ixmlNode_appendChild((IXML_Node*)doc,(IXML_Node*)top);
+    
+    return doc;
+}
+
+// Decoding UPnP Event data. The variable values are contained in a
+// propertyset XML document:
+//     <?xml version="1.0"?>
+//     <e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
+//       <e:property>
+//         <variableName>new value</variableName>
+//       </e:property>
+//       <!-- Other variable names and values (if any) go here. -->
+//     </e:propertyset>
+
+bool decodePropertySet(IXML_Document *doc, 
+                       unordered_map<string,string>& out)
+{
+    bool ret = false;
+    IXML_Node* topNode = ixmlNode_getFirstChild((IXML_Node *)doc);
+    if (topNode == 0) {
+        LOGERR("decodePropertySet: (no topNode) ??" << endl);
+        return false;
+    }
+    //LOGDEB("decodePropertySet: topnode name: " << 
+    //       ixmlNode_getNodeName(topNode) << endl);
+
+    IXML_NodeList* nl = ixmlNode_getChildNodes(topNode);
+    if (nl == 0) {
+        LOGDEB("decodePropertySet: empty list" << endl);
+        return true;
+    }
+    for (unsigned long i = 0; i <  ixmlNodeList_length(nl); i++) {
+        IXML_Node *cld = ixmlNodeList_item(nl, i);
+        if (cld == 0) {
+            LOGDEB("decodePropertySet: got null node  from nlist at index " <<
+                   i << " ??" << endl);
+            // Seems to happen with empty arg list?? This looks like a bug, 
+            // should we not get an empty node instead?
+            if (i == 0) {
+                ret = true;
+            }
+            goto out;
+        }
+        const char *name = ixmlNode_getNodeName(cld);
+        //LOGDEB("decodePropertySet: got node name:     " << 
+        //   ixmlNode_getNodeName(cld) << endl);
+        if (cld == 0) {
+            DOMString pnode = ixmlPrintNode(cld);
+            //LOGDEB("decodePropertySet: got null name ??:" << pnode << endl);
+            ixmlFreeDOMString(pnode);
+            goto out;
+        }
+        IXML_Node *subnode = ixmlNode_getFirstChild(cld);
+        name = ixmlNode_getNodeName(subnode);
+        //LOGDEB("decodePropertySet: got subnode name:         " << 
+        //   name << endl);
+        
+        IXML_Node *txtnode = ixmlNode_getFirstChild(subnode);
+        //LOGDEB("decodePropertySet: got txtnode name:             " << 
+        //   ixmlNode_getNodeName(txtnode) << endl);
+        
+        const char *value = "";
+        if (txtnode != 0) {
+            value = ixmlNode_getNodeValue(txtnode);
+        }
+        // Can we get an empty value here ?
+        if (value == 0)
+            value = "";
+        // ixml does the unquoting. Don't call xmlUnquote here
+        out[name] = value; 
+    }
+
+    ret = true;
+out:
+    if (nl)
+        ixmlNodeList_free(nl);
+    return ret;
+}
+
+} // namespace
src/base64.cxx to libupnpp/base64.cxx
File was renamed.
src/base64.hxx to libupnpp/base64.hxx
File was renamed.
src/config.h.in to libupnpp/config.h.in
File was renamed.
src/expatmm.hxx to libupnpp/workqueue.hxx
--- a/src/expatmm.hxx
+++ b/libupnpp/workqueue.hxx
@@ -1,312 +1,328 @@
-/*
- * ExpatMM - C++ Wrapper for Expat available at http://expat.sourceforge.net/
- * Copyright (c) 2006, 2007, 2008, 2009 IntelliTree Solutions llc
- * Author: Coleman Kane <ckane@intellitree.com>
+/*	 Copyright (C) 2012 J.F.Dockes
+ *	 This program is free software; you can redistribute it and/or modify
+ *	 it under the terms of the GNU General Public License as published by
+ *	 the Free Software Foundation; either version 2 of the License, or
+ *	 (at your option) any later version.
  *
- * Mutilated and forced into single-file solution by <jf@dockes.org>
- * Copyright (c) 2013 J.F. Dockes
+ *	 This program is distributed in the hope that it will be useful,
+ *	 but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	 GNU General Public License for more details.
  *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
+ *	 You should have received a copy of the GNU General Public License
+ *	 along with this program; if not, write to the
+ *	 Free Software Foundation, Inc.,
+ *	 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+#ifndef _WORKQUEUE_H_INCLUDED_
+#define _WORKQUEUE_H_INCLUDED_
+
+#include <pthread.h>
+#include <time.h>
+
+#include <string>
+#include <queue>
+#include <unordered_map>
+
+#include "ptmutex.hxx"
+
+namespace UPnPP {
+
+/// Store per-worker-thread data. Just an initialized timespec, and
+/// used at the moment.
+class WQTData {
+public:
+	WQTData() {wstart.tv_sec = 0; wstart.tv_nsec = 0;}
+	struct timespec wstart;
+};
+
+/**
+ * A WorkQueue manages the synchronisation around a queue of work items,
+ * where a number of client threads queue tasks and a number of worker
+ * threads take and execute them. The goal is to introduce some level
+ * of parallelism between the successive steps of a previously single
+ * threaded pipeline. For example data extraction / data preparation / index
+ * update, but this could have other uses.
  *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General
- * Public License along with this library; if not, please feel free
- * to contact the author listed above.
+ * There is no individual task status return. In case of fatal error,
+ * the client or worker sets an end condition on the queue. A second
+ * queue could conceivably be used for returning individual task
+ * status.
  */
-#ifndef _EXPATMM_EXPATXMLPARSER_H
-#define _EXPATMM_EXPATXMLPARSER_H
-
-#include <string.h>
-#include <expat.h>
-
-namespace UPnPP {
-
-class ExpatXMLParser {
+template <class T> class WorkQueue {
 public:
 
-    /* Create a new parser, using the default Chunk Size */
-    ExpatXMLParser(void)
-        {
-            init();
-        }
-
-    /* Create a new parser, using a user-supplied chunk size */
-    ExpatXMLParser(size_t chunk_size)
-        {
-            init(chunk_size);
-        }
-
-    /* Destructor that cleans up xml_buffer and parser */
-    virtual ~ExpatXMLParser(void)
-        {
-            valid_parser = false;
-            if(expat_parser != NULL) {
-                XML_ParserFree(expat_parser);
-                expat_parser = NULL;
-            }
-            if(xml_buffer != NULL) {
-                delete xml_buffer;
-                xml_buffer = NULL;
-            }
-        }
-
-    /*
-      Generic Parser. Most derivations will simply call this, rather
-      than implement their own. This will loop, processing XML data
-      and calling the necessary handler code until an error is encountered.
-    */
-    virtual bool Parse(void)
-        {
-            /* Ensure that the parser is ready */
-            if(!Ready())
-                return false;
-
-            ssize_t bytes_read;
-            /* Loop, reading the XML source block by block */
-            while((bytes_read = read_block()) >= 0) {
-                if(bytes_read > 0) {
-                    XML_Status local_status =
-                        XML_Parse(expat_parser, getReadBuffer(), bytes_read,
-                                  XML_FALSE);
-
-                    if(local_status != XML_STATUS_OK) {
-                        status = local_status;
-                        last_error = XML_GetErrorCode(expat_parser);
-                        break;
-                    }
-
-                    /* Break on successful "short read", in event of EOF */
-                    if(getLastError() == XML_ERROR_FINISHED)
-                        break;
-                }
-            }
-
-            /* Finalize the parser */
-            if((getStatus() == XML_STATUS_OK) ||
-               (getLastError() == XML_ERROR_FINISHED)) {
-                XML_Parse(expat_parser, getBuffer(), 0, XML_TRUE);
-                return true;
-            }
-
-            /* Return false in the event of an error. The parser is
-               not finalized on error. */
-            return false;
-        }
-
-    /* Expose status, error, and control codes to users */
-    virtual bool Ready(void) { return valid_parser; };
-    virtual XML_Error getLastError(void) { return last_error; };
-    virtual XML_Status getStatus(void) { return status; };
-
-protected:
-    virtual XML_Char *getBuffer(void) { return xml_buffer; };
-    virtual const char *getReadBuffer(void) { return xml_buffer; };
-    virtual size_t getBlockSize(void) { return xml_buffer_size; };
-
-    /* Read XML data.
-     *
-     * Override this to implement your container-specific parser.
-     *
-     * You must:
-     * put new XML data into xml_buffer
-     * set status
-     * set last_error
-     * return the amount of XML_Char's written to xml_buffer
-     *
-     * on error, return < 0. The contents of xml_buffer will be
-     * thrown away in this event, so it is the derived class's
-     * responsibility to reseek the "data cursor" to re-get any
-     * data in the buffer on an error condition.
-     *
-     * Use setReadiness, setStatus, and setLastError to handle
-     * error, status, and control events and codes.
-     *
-     * The default implementation returns "no elements" if it is
-     * ever called. and should be overridden by the derived class.
-     *
-     * Note that, as the actual parser only uses
-     * getBuffer()/getBlockSize()/read_block() (no direct access
-     * to the buffer), you are free to use an entirely different
-     * I/O mechanism, like what does the inputRefXMLParser below.
-     */
-    virtual ssize_t read_block(void)
-        {
-            last_error = XML_ERROR_NO_ELEMENTS;
-            status = XML_STATUS_ERROR;
-            return -1;
-        }
-
-    virtual void setReadiness(bool ready)
-        {
-            valid_parser = ready;
-        }
-    virtual void setStatus(XML_Status new_status)
-        {
-            status = new_status;
-        }
-    virtual void setLastError(XML_Error new_last_error)
-        {
-            last_error = new_last_error;
-        };
-
-    /* Methods to be overriden */
-    virtual void StartElement(const XML_Char *,
-                              const XML_Char **) {}
-    virtual void EndElement(const XML_Char *) {}
-    virtual void CharacterData(const XML_Char *, int) {}
-    virtual void ProcessingInstruction(const XML_Char *,
-                                       const XML_Char *) {}
-    virtual void CommentData(const XML_Char *) {}
-    virtual void DefaultHandler(const XML_Char *, int) {}
-    virtual void CDataStart(void) {}
-    virtual void CDataEnd(void) {}
-
-    /* The handle for the parser (expat) */
-    XML_Parser expat_parser;
+	/** Create a WorkQueue
+	 * @param name for message printing
+	 * @param hi number of tasks on queue before clients blocks. Default 0
+	 *	  meaning no limit. hi == -1 means that the queue is disabled.
+	 * @param lo minimum count of tasks before worker starts. Default 1.
+	 */
+	WorkQueue(const std::string& name, size_t hi = 0, size_t lo = 1)
+		: m_name(name), m_high(hi), m_low(lo),
+		  m_workers_exited(0), m_clients_waiting(0), m_workers_waiting(0),
+		  m_tottasks(0), m_nowake(0), m_workersleeps(0), m_clientsleeps(0)
+	{
+		m_ok = (pthread_cond_init(&m_ccond, 0) == 0) &&
+			(pthread_cond_init(&m_wcond, 0) == 0);
+	}
+
+	~WorkQueue()
+	{
+		if (!m_worker_threads.empty())
+			setTerminateAndWait();
+	}
+
+	/** Start the worker threads.
+	 *
+	 * @param nworkers number of threads copies to start.
+	 * @param start_routine thread function. It should loop
+	 *		taking (QueueWorker::take()) and executing tasks.
+	 * @param arg initial parameter to thread function.
+	 * @return true if ok.
+	 */
+	bool start(int nworkers, void *(*workproc)(void *), void *arg)
+	{
+		PTMutexLocker lock(m_mutex);
+		for	 (int i = 0; i < nworkers; i++) {
+			int err;
+			pthread_t thr;
+			if ((err = pthread_create(&thr, 0, workproc, arg))) {
+				return false;
+			}
+			m_worker_threads.insert(std::pair<pthread_t, WQTData>(thr, WQTData()));
+		}
+		return true;
+	}
+
+	/** Add item to work queue, called from client.
+	 *
+	 * Sleeps if there are already too many.
+	 */
+	bool put(T t, bool flushprevious = false)
+	{
+		PTMutexLocker lock(m_mutex);
+		if (!lock.ok() || !ok()) {
+			return false;
+		}
+
+		while (ok() && m_high > 0 && m_queue.size() >= m_high) {
+			m_clientsleeps++;
+			// Keep the order: we test ok() AFTER the sleep...
+			m_clients_waiting++;
+			if (pthread_cond_wait(&m_ccond, lock.getMutex()) || !ok()) {
+				m_clients_waiting--;
+				return false;
+			}
+			m_clients_waiting--;
+		}
+		if (flushprevious) {
+			while (!m_queue.empty())
+				m_queue.pop();
+		}
+		m_queue.push(t);
+		if (m_workers_waiting > 0) {
+			// Just wake one worker, there is only one new task.
+			pthread_cond_signal(&m_wcond);
+		} else {
+			m_nowake++;
+		}
+
+		return true;
+	}
+
+	/** Wait until the queue is inactive. Called from client.
+	 *
+	 * Waits until the task queue is empty and the workers are all
+	 * back sleeping. Used by the client to wait for all current work
+	 * to be completed, when it needs to perform work that couldn't be
+	 * done in parallel with the worker's tasks, or before shutting
+	 * down. Work can be resumed after calling this. Note that the
+	 * only thread which can call it safely is the client just above
+	 * (which can control the task flow), else there could be
+	 * tasks in the intermediate queues.
+	 * To rephrase: there is no warranty on return that the queue is actually
+	 * idle EXCEPT if the caller knows that no jobs are still being created.
+	 * It would be possible to transform this into a safe call if some kind
+	 * of suspend condition was set on the queue by waitIdle(), to be reset by
+	 * some kind of "resume" call. Not currently the case.
+	 */
+	bool waitIdle()
+	{
+		PTMutexLocker lock(m_mutex);
+		if (!lock.ok() || !ok()) {
+			return false;
+		}
+
+		// We're done when the queue is empty AND all workers are back
+		// waiting for a task.
+		while (ok() && (m_queue.size() > 0 ||
+						m_workers_waiting != m_worker_threads.size())) {
+			m_clients_waiting++;
+			if (pthread_cond_wait(&m_ccond, lock.getMutex())) {
+				m_clients_waiting--;
+				m_ok = false;
+				return false;
+			}
+			m_clients_waiting--;
+		}
+
+		return ok();
+	}
+
+
+	/** Tell the workers to exit, and wait for them.
+	 *
+	 * Does not bother about tasks possibly remaining on the queue, so
+	 * should be called after waitIdle() for an orderly shutdown.
+	 */
+	void* setTerminateAndWait()
+	{
+		PTMutexLocker lock(m_mutex);
+
+		if (m_worker_threads.empty()) {
+			// Already called ?
+			return (void*)0;
+		}
+
+		// Wait for all worker threads to have called workerExit()
+		m_ok = false;
+		while (m_workers_exited < m_worker_threads.size()) {
+			pthread_cond_broadcast(&m_wcond);
+			m_clients_waiting++;
+			if (pthread_cond_wait(&m_ccond, lock.getMutex())) {
+				m_clients_waiting--;
+				return (void*)0;
+			}
+			m_clients_waiting--;
+		}
+
+		// Perform the thread joins and compute overall status
+		// Workers return (void*)1 if ok
+		void *statusall = (void*)1;
+		std::unordered_map<pthread_t,  WQTData>::iterator it;
+		while (!m_worker_threads.empty()) {
+			void *status;
+			it = m_worker_threads.begin();
+			pthread_join(it->first, &status);
+			if (status == (void *)0)
+				statusall = status;
+			m_worker_threads.erase(it);
+		}
+
+		// Reset to start state.
+		m_workers_exited = m_clients_waiting = m_workers_waiting =
+			m_tottasks = m_nowake = m_workersleeps = m_clientsleeps = 0;
+		m_ok = true;
+
+		return statusall;
+	}
+
+	/** Take task from queue. Called from worker.
+	 *
+	 * Sleeps if there are not enough. Signal if we go to sleep on empty
+	 * queue: client may be waiting for our going idle.
+	 */
+	bool take(T* tp, size_t *szp = 0)
+	{
+		PTMutexLocker lock(m_mutex);
+		if (!lock.ok() || !ok()) {
+			return false;
+		}
+
+		while (ok() && m_queue.size() < m_low) {
+			m_workersleeps++;
+			m_workers_waiting++;
+			if (m_queue.empty())
+				pthread_cond_broadcast(&m_ccond);
+			if (pthread_cond_wait(&m_wcond, lock.getMutex()) || !ok()) {
+				m_workers_waiting--;
+				return false;
+			}
+			m_workers_waiting--;
+		}
+
+		m_tottasks++;
+		*tp = m_queue.front();
+		if (szp)
+			*szp = m_queue.size();
+		m_queue.pop();
+		if (m_clients_waiting > 0) {
+			// No reason to wake up more than one client thread
+			pthread_cond_signal(&m_ccond);
+		} else {
+			m_nowake++;
+		}
+		return true;
+	}
+
+	/** Advertise exit and abort queue. Called from worker
+	 *
+	 * This would happen after an unrecoverable error, or when
+	 * the queue is terminated by the client. Workers never exit normally,
+	 * except when the queue is shut down (at which point m_ok is set to
+	 * false by the shutdown code anyway). The thread must return/exit
+	 * immediately after calling this.
+	 */
+	void workerExit()
+	{
+		PTMutexLocker lock(m_mutex);
+		m_workers_exited++;
+		m_ok = false;
+		pthread_cond_broadcast(&m_ccond);
+	}
+
+	size_t qsize()
+	{
+		PTMutexLocker lock(m_mutex);
+		size_t sz = m_queue.size();
+		return sz;
+	}
 
 private:
-
-    /* Temporary buffer where data is streamed in */
-    XML_Char *xml_buffer;
-    size_t xml_buffer_size;
-
-    /* Tells if the parser is ready to accept data */
-    bool valid_parser;
-
-    /* Status and Error codes in the event of unforseen events */
-    XML_Status status;
-    XML_Error last_error;
-
-    /* Expat callbacks.
-     * The expatmm protocol is to pass (this) as the userData argument
-     * in the XML_Parser structure. These static methods will convert
-     * handlers into upcalls to the instantiated class's virtual members
-     * to do the actual handling work. */
-    static void _element_start_handler(void *userData, const XML_Char *name,
-                                       const XML_Char **atts)
-        {
-            ExpatXMLParser *me = (ExpatXMLParser*)userData;
-            if(me != NULL) me->StartElement(name, atts);
-        }
-    static void _element_end_handler(void *userData, const XML_Char *name)
-        {
-            ExpatXMLParser *me = (ExpatXMLParser*)userData;
-            if(me != NULL) me->EndElement(name);
-        }
-    static void _character_data_handler(void *userData,
-                                        const XML_Char *s, int len)
-        {
-            ExpatXMLParser *me = (ExpatXMLParser*)userData;
-            if(me != NULL) me->CharacterData(s, len);
-        }
-    static void _processing_instr_handler(void *userData,
-                                          const XML_Char *target,
-                                          const XML_Char *data)
-        {
-            ExpatXMLParser *me = (ExpatXMLParser*)userData;
-            if(me != NULL) me->ProcessingInstruction(target, data);
-        }
-    static void _comment_handler(void *userData, const XML_Char *data)
-        {
-            ExpatXMLParser *me = (ExpatXMLParser*)userData;
-            if(me != NULL) me->CommentData(data);
-        }
-    static void _default_handler(void *userData, const XML_Char *s, int len)
-        {
-            ExpatXMLParser *me = (ExpatXMLParser*)userData;
-            if(me != NULL) me->DefaultHandler(s, len);
-        }
-    static void _cdata_start_handler(void *userData)
-        {
-            ExpatXMLParser *me = (ExpatXMLParser*)userData;
-            if(me != NULL) me->CDataStart();
-        }
-    static void _cdata_end_handler(void *userData)
-        {
-            ExpatXMLParser *me = (ExpatXMLParser*)userData;
-            if(me != NULL) me->CDataEnd();
-        }
-    /* Register our static handlers with the Expat events. */
-    void register_default_handlers()
-        {
-            XML_SetElementHandler(expat_parser, &_element_start_handler,
-                                  &_element_end_handler);
-            XML_SetCharacterDataHandler(expat_parser, &_character_data_handler);
-            XML_SetProcessingInstructionHandler(expat_parser,
-                                                &_processing_instr_handler);
-            XML_SetCommentHandler(expat_parser, &_comment_handler);
-            XML_SetCdataSectionHandler(expat_parser, &_cdata_start_handler,
-                                       &_cdata_end_handler);
-            XML_SetDefaultHandler(expat_parser, &_default_handler);
-        }
-    /* Constructor common code */
-    void init(size_t chunk_size = 0)
-        {
-            valid_parser = false;
-            expat_parser = NULL;
-            xml_buffer_size = chunk_size ? chunk_size : 10240;
-            xml_buffer = new XML_Char[xml_buffer_size];
-            if(xml_buffer == NULL)
-                return;
-            expat_parser = XML_ParserCreate(NULL);
-
-            if(expat_parser == NULL) {
-                delete xml_buffer;
-                xml_buffer = NULL;
-                return;
-            }
-            status = XML_STATUS_OK;
-            last_error = XML_ERROR_NONE;
-
-            memset(xml_buffer, 0, chunk_size * sizeof(XML_Char));
-
-            /* Set the "ready" flag on this parser */
-            valid_parser = true;
-            XML_SetUserData(expat_parser, (void*)this);
-            register_default_handlers();
-        }
+	bool ok()
+	{
+		bool isok = m_ok && m_workers_exited == 0 && !m_worker_threads.empty();
+		return isok;
+	}
+
+	long long nanodiff(const struct timespec& older,
+					   const struct timespec& newer)
+	{
+		return (newer.tv_sec - older.tv_sec) * 1000000000LL
+			+ newer.tv_nsec - older.tv_nsec;
+	}
+
+	// Configuration
+	std::string m_name;
+	size_t m_high;
+	size_t m_low;
+
+	// Status
+	// Worker threads having called exit
+	unsigned int m_workers_exited;
+	bool m_ok;
+
+	// Per-thread data. The data is not used currently, this could be
+	// a set<pthread_t>
+	std::unordered_map<pthread_t, WQTData> m_worker_threads;
+
+	// Synchronization
+	std::queue<T> m_queue;
+	pthread_cond_t m_ccond;
+	pthread_cond_t m_wcond;
+	PTMutexInit m_mutex;
+	// Client/Worker threads currently waiting for a job
+	unsigned int m_clients_waiting;
+	unsigned int m_workers_waiting;
+
+	// Statistics
+	unsigned int m_tottasks;
+	unsigned int m_nowake;
+	unsigned int m_workersleeps;
+	unsigned int m_clientsleeps;
 };
 
-/** A specialization of ExpatXMLParser that does not copy its input */
-class inputRefXMLParser : public ExpatXMLParser {
-public:
-    // Beware: we only use a ref to input to minimize copying. This means
-    // that storage for the input parameter must persist until you are done
-    // with the parser object !
-    inputRefXMLParser(const std::string& input)
-        : ExpatXMLParser(1), // Have to allocate a small buf even if not used.
-          m_input(input)
-        {}
-
-protected:
-    ssize_t read_block(void)
-        {
-            if (getLastError() == XML_ERROR_FINISHED) {
-                setStatus(XML_STATUS_OK);
-                return -1;
-            }
-            setLastError(XML_ERROR_FINISHED);
-            return m_input.size();
-        }
-    const char *getReadBuffer()
-        {
-            return m_input.c_str();
-        }
-    virtual size_t getBlockSize(void)
-        {
-            return m_input.size();
-        }
-protected:
-    const std::string& m_input;
-};
-
-}; // End namespace 
-
-#endif /* _EXPATMM_EXPATXMLPARSER_H */
+} // namespace
+
+#endif /* _WORKQUEUE_H_INCLUDED_ */
src/ixmlwrap.cxx to libupnpp/ptmutex.hxx
--- a/src/ixmlwrap.cxx
+++ b/libupnpp/ptmutex.hxx
@@ -1,66 +1,67 @@
-/* Copyright (C) 2013 J.F.Dockes
- *       This program is free software; you can redistribute it and/or modify
- *       it under the terms of the GNU General Public License as published by
- *       the Free Software Foundation; either version 2 of the License, or
- *       (at your option) any later version.
+/* Copyright (C) 2011 J.F.Dockes
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
  *
- *       This program is distributed in the hope that it will be useful,
- *       but WITHOUT ANY WARRANTY; without even the implied warranty of
- *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *       GNU General Public License for more details.
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
  *
- *       You should have received a copy of the GNU General Public License
- *       along with this program; if not, write to the
- *       Free Software Foundation, Inc.,
- *       59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the
+ *   Free Software Foundation, Inc.,
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
-#include "config.h"
+#ifndef _PTMUTEX_H_INCLUDED_
+#define _PTMUTEX_H_INCLUDED_
 
-#include "libupnpp/ixmlwrap.hxx"
+#include <pthread.h>
 
-#include <upnp/ixml.h>                  // for IXML_Node, etc
-
-#include <string>                       // for string
-
-using std::string;
+/// A trivial wrapper/helper for pthread mutex locks
 
 namespace UPnPP {
 
-#if notUsedAnyMore
-// Get the value for the first element in the document with the given name.
-// There should be only one such element for this to make any sense.
-string getFirstElementValue(IXML_Document *doc, const string& name)
-{
-    string ret;
-    IXML_NodeList *nodes =
-        ixmlDocument_getElementsByTagName(doc, name.c_str());
+/// Lock storage with auto-initialization. Must be created before any
+/// lock-using thread of course (possibly as a static object).
+class PTMutexInit {
+public:
+    pthread_mutex_t m_mutex;
+    int m_status;
+    PTMutexInit() {
+        m_status = pthread_mutex_init(&m_mutex, 0);
+    }
+};
 
-    if (nodes) {
-        IXML_Node *first = ixmlNodeList_item(nodes, 0);
-        if (first) {
-            IXML_Node *dnode = ixmlNode_getFirstChild(first);
-            if (dnode) {
-                ret = ixmlNode_getNodeValue(dnode);
-            }
+/// Take the lock when constructed, release when deleted. Can be disabled
+/// by constructor params for conditional use.
+class PTMutexLocker {
+public:
+    // The nolock arg enables conditional locking
+    PTMutexLocker(PTMutexInit& l, bool nolock = false)
+        : m_lock(l), m_status(-1) {
+        if (!nolock) {
+            m_status = pthread_mutex_lock(&m_lock.m_mutex);
         }
     }
+    ~PTMutexLocker() {
+        if (m_status == 0) {
+            pthread_mutex_unlock(&m_lock.m_mutex);
+        }
+    }
+    int ok() {
+        return m_status == 0;
+    }
+    // For pthread_cond_wait etc.
+    pthread_mutex_t *getMutex() {
+        return &m_lock.m_mutex;
+    }
+private:
+    PTMutexInit& m_lock;
+    int m_status;
+};
 
-    if(nodes)
-        ixmlNodeList_free(nodes);
-    return ret;
-}
-#endif
+} // namespace UPnPP
 
-string ixmlwPrintDoc(IXML_Document* doc)
-{
-    DOMString s = ixmlPrintDocument(doc);
-    if (s) {
-        string cpps(s);
-        ixmlFreeDOMString(s);
-        return cpps;
-    } else {
-        return string();
-    }
-}
-
-}
+#endif /* _PTMUTEX_H_INCLUDED_ */
src/ixmlwrap.hxx to libupnpp/log.cxx
--- a/src/ixmlwrap.hxx
+++ b/libupnpp/log.cxx
@@ -1,40 +1,53 @@
-/* Copyright (C) 2013 J.F.Dockes
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 2 of the License, or
- *   (at your option) any later version.
+/* Copyright (C) 2014 J.F.Dockes
+ *	 This program is free software; you can redistribute it and/or modify
+ *	 it under the terms of the GNU General Public License as published by
+ *	 the Free Software Foundation; either version 2 of the License, or
+ *	 (at your option) any later version.
  *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
+ *	 This program is distributed in the hope that it will be useful,
+ *	 but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	 GNU General Public License for more details.
  *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the
- *   Free Software Foundation, Inc.,
- *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *	 You should have received a copy of the GNU General Public License
+ *	 along with this program; if not, write to the
+ *	 Free Software Foundation, Inc.,
+ *	 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
-#ifndef _IXMLWRAP_H_INCLUDED_
-#define _IXMLWRAP_H_INCLUDED_
+#include "config.h"
 
-#include <upnp/ixml.h>                  // for IXML_Document
+#include "log.hxx"
 
-#include <string>                       // for string
+#include <errno.h>                      // for errno
+
+#include <fstream>                      // for operator<<, basic_ostream, etc
+
+using namespace std;
 
 namespace UPnPP {
 
-#if notUsedAnyMore
-    /** Retrieve the text content for the first element of given name.
-     * Returns an empty string if the element does not contain a text node */
-    std::string getFirstElementValue(IXML_Document *doc, 
-                                     const std::string& name); *
-#endif
+Logger::Logger(const std::string& fn) 
+    : m_tocerr(false), m_loglevel(LLDEB)
+{
+    if (!fn.empty() && fn.compare("stderr")) {
+        m_stream.open(fn, std::fstream::out | std::ofstream::trunc);
+        if (!m_stream.is_open()) {
+            cerr << "Logger::Logger: log open failed: for [" <<
+                fn << "] errno " << errno << endl;
+            m_tocerr = true;
+        }
+    } else {
+        m_tocerr = true;
+    }
+}
 
-    /** Return the result of ixmlPrintDocument as a string and take
-     * care of freeing the memory. This is inefficient of course (one
-     * more alloc+copy), and destined to debug statements */
-    std::string ixmlwPrintDoc(IXML_Document*);
+static Logger *theLog;
+
+Logger *Logger::getTheLog(const string& fn)
+{
+    if (theLog == 0)
+        theLog = new Logger(fn);
+    return theLog;
+}
 
 }
-
-#endif /* _IXMLWRAP_H_INCLUDED_ */
src/log.cxx to libupnpp/upnpavutils.cxx
--- a/src/log.cxx
+++ b/libupnpp/upnpavutils.cxx
@@ -1,53 +1,56 @@
 /* Copyright (C) 2014 J.F.Dockes
- *	 This program is free software; you can redistribute it and/or modify
- *	 it under the terms of the GNU General Public License as published by
- *	 the Free Software Foundation; either version 2 of the License, or
- *	 (at your option) any later version.
+ *       This program is free software; you can redistribute it and/or modify
+ *       it under the terms of the GNU General Public License as published by
+ *       the Free Software Foundation; either version 2 of the License, or
+ *       (at your option) any later version.
  *
- *	 This program is distributed in the hope that it will be useful,
- *	 but WITHOUT ANY WARRANTY; without even the implied warranty of
- *	 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *	 GNU General Public License for more details.
+ *       This program is distributed in the hope that it will be useful,
+ *       but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *       GNU General Public License for more details.
  *
- *	 You should have received a copy of the GNU General Public License
- *	 along with this program; if not, write to the
- *	 Free Software Foundation, Inc.,
- *	 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *       You should have received a copy of the GNU General Public License
+ *       along with this program; if not, write to the
+ *       Free Software Foundation, Inc.,
+ *       59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
-#include "config.h"
+#include "libupnpp/upnpavutils.hxx"
 
-#include "log.hxx"
-
-#include <errno.h>                      // for errno
-
-#include <fstream>                      // for operator<<, basic_ostream, etc
+#include <cstdio>
+#include <string>
 
 using namespace std;
 
 namespace UPnPP {
 
-Logger::Logger(const std::string& fn) 
-    : m_tocerr(false), m_loglevel(LLDEB)
+// Format duration in milliseconds into UPnP duration format
+string upnpduration(int ms)
 {
-    if (!fn.empty() && fn.compare("stderr")) {
-        m_stream.open(fn, std::fstream::out | std::ofstream::trunc);
-        if (!m_stream.is_open()) {
-            cerr << "Logger::Logger: log open failed: for [" <<
-                fn << "] errno " << errno << endl;
-            m_tocerr = true;
-        }
-    } else {
-        m_tocerr = true;
-    }
+    int hours = ms / (3600 * 1000);
+    ms -= hours * 3600 * 1000;
+    int minutes = ms / (60 * 1000);
+    ms -= minutes * 60 * 1000;
+    int secs = ms / 1000;
+    ms -= secs * 1000;
+
+    char cbuf[100];
+
+    // This is the format from the ref doc, but it appears that the
+    // decimal part in the seconds field is an issue with some control
+    // points. So drop it...
+    //  sprintf(cbuf, "%d:%02d:%02d.%03d", hours, minutes, secs, ms);
+    sprintf(cbuf, "%d:%02d:%02d", hours, minutes, secs);
+    return cbuf;
 }
 
-static Logger *theLog;
-
-Logger *Logger::getTheLog(const string& fn)
+// H:M:S to seconds
+int upnpdurationtos(const string& dur)
 {
-    if (theLog == 0)
-        theLog = new Logger(fn);
-    return theLog;
+    int hours, minutes, seconds;
+    if (sscanf(dur.c_str(), "%d:%d:%d", &hours, &minutes, &seconds) != 3) {
+	return 0;
+    }
+    return 3600 * hours + 60 * minutes + seconds;
 }
 
 }
src/log.hxx to libupnpp/log.hxx
File was renamed.
src/md5.cxx to libupnpp/md5.cxx
File was renamed.
src/md5.hxx to libupnpp/md5.hxx
File was renamed.
src/ptmutex.hxx to libupnpp/upnpp_p.hxx
--- a/src/ptmutex.hxx
+++ b/libupnpp/upnpp_p.hxx
@@ -1,4 +1,4 @@
-/* Copyright (C) 2011 J.F.Dockes
+/* Copyright (C) 2013 J.F.Dockes
  *   This program is free software; you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License as published by
  *   the Free Software Foundation; either version 2 of the License, or
@@ -14,54 +14,34 @@
  *   Free Software Foundation, Inc.,
  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
-#ifndef _PTMUTEX_H_INCLUDED_
-#define _PTMUTEX_H_INCLUDED_
 
-#include <pthread.h>
+#ifndef _UPNPP_H_X_INCLUDED_
+#define _UPNPP_H_X_INCLUDED_
+#include "config.h"
 
-/// A trivial wrapper/helper for pthread mutex locks
+/* Private shared defs for the library. Clients need not and should
+   not include this */
+
+#include <string>
 
 namespace UPnPP {
 
-/// Lock storage with auto-initialization. Must be created before any
-/// lock-using thread of course (possibly as a static object).
-class PTMutexInit {
-public:
-    pthread_mutex_t m_mutex;
-    int m_status;
-    PTMutexInit() {
-        m_status = pthread_mutex_init(&m_mutex, 0);
-    }
-};
+// Concatenate paths. Caller should make sure it makes sense.
+extern std::string caturl(const std::string& s1, const std::string& s2);
+// Return the scheme://host:port[/] part of input, or input if it is weird
+extern std::string baseurl(const std::string& url);
+extern void trimstring(std::string &s, const char *ws = " \t\n");
+extern std::string path_getfather(const std::string &s);
+extern std::string path_getsimple(const std::string &s);
+template <class T> bool csvToStrings(const std::string& s, T &tokens);
 
-/// Take the lock when constructed, release when deleted. Can be disabled
-/// by constructor params for conditional use.
-class PTMutexLocker {
-public:
-    // The nolock arg enables conditional locking
-    PTMutexLocker(PTMutexInit& l, bool nolock = false)
-        : m_lock(l), m_status(-1) {
-        if (!nolock) {
-            m_status = pthread_mutex_lock(&m_lock.m_mutex);
-        }
-    }
-    ~PTMutexLocker() {
-        if (m_status == 0) {
-            pthread_mutex_unlock(&m_lock.m_mutex);
-        }
-    }
-    int ok() {
-        return m_status == 0;
-    }
-    // For pthread_cond_wait etc.
-    pthread_mutex_t *getMutex() {
-        return &m_lock.m_mutex;
-    }
-private:
-    PTMutexInit& m_lock;
-    int m_status;
-};
+// @return false if s does not look like a bool at all (does not begin
+// with [FfNnYyTt01]
+extern bool stringToBool(const std::string& s, bool *v);
 
-} // namespace UPnPP
+// Case-insensitive ascii string compare where s1 is already upper-case
+int stringuppercmp(const std::string &s1, const std::string& s2);
 
-#endif /* _PTMUTEX_H_INCLUDED_ */
+} // namespace
+
+#endif /* _UPNPP_H_X_INCLUDED_ */
src/upnpavutils.cxx to libupnpp/upnpavutils.hxx
--- a/src/upnpavutils.cxx
+++ b/libupnpp/upnpavutils.hxx
@@ -14,43 +14,19 @@
  *       Free Software Foundation, Inc.,
  *       59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
-#include "libupnpp/upnpavutils.hxx"
+#ifndef _UPNPAVUTILS_HXX_INCLUDED_
+#define _UPNPAVUTILS_HXX_INCLUDED_
 
-#include <cstdio>
 #include <string>
-
-using namespace std;
 
 namespace UPnPP {
 
-// Format duration in milliseconds into UPnP duration format
-string upnpduration(int ms)
-{
-    int hours = ms / (3600 * 1000);
-    ms -= hours * 3600 * 1000;
-    int minutes = ms / (60 * 1000);
-    ms -= minutes * 60 * 1000;
-    int secs = ms / 1000;
-    ms -= secs * 1000;
+/** Format milliseconds into H+:MM:SS */
+extern std::string upnpduration(int ms);
 
-    char cbuf[100];
+/** H+:MM:SS to seconds */
+extern int upnpdurationtos(const std::string& dur);
 
-    // This is the format from the ref doc, but it appears that the
-    // decimal part in the seconds field is an issue with some control
-    // points. So drop it...
-    //  sprintf(cbuf, "%d:%02d:%02d.%03d", hours, minutes, secs, ms);
-    sprintf(cbuf, "%d:%02d:%02d", hours, minutes, secs);
-    return cbuf;
 }
 
-// H:M:S to seconds
-int upnpdurationtos(const string& dur)
-{
-    int hours, minutes, seconds;
-    if (sscanf(dur.c_str(), "%d:%d:%d", &hours, &minutes, &seconds) != 3) {
-	return 0;
-    }
-    return 3600 * hours + 60 * minutes + seconds;
-}
-
-}
+#endif /* _UPNPAVUTILS_HXX_INCLUDED_ */
src/upnpavutils.hxx to libupnpp/upnpputils.hxx
--- a/src/upnpavutils.hxx
+++ b/libupnpp/upnpputils.hxx
@@ -1,32 +1,26 @@
-/* Copyright (C) 2014 J.F.Dockes
- *       This program is free software; you can redistribute it and/or modify
- *       it under the terms of the GNU General Public License as published by
- *       the Free Software Foundation; either version 2 of the License, or
- *       (at your option) any later version.
+/* Copyright (C) 2013 J.F.Dockes
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
  *
- *       This program is distributed in the hope that it will be useful,
- *       but WITHOUT ANY WARRANTY; without even the implied warranty of
- *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *       GNU General Public License for more details.
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
  *
- *       You should have received a copy of the GNU General Public License
- *       along with this program; if not, write to the
- *       Free Software Foundation, Inc.,
- *       59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the
+ *   Free Software Foundation, Inc.,
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
-#ifndef _UPNPAVUTILS_HXX_INCLUDED_
-#define _UPNPAVUTILS_HXX_INCLUDED_
-
-#include <string>
+#ifndef _UPNPPUTILS_H_X_INCLUDED_
+#define _UPNPPUTILS_H_X_INCLUDED_
 
 namespace UPnPP {
 
-/** Format milliseconds into H+:MM:SS */
-extern std::string upnpduration(int ms);
-
-/** H+:MM:SS to seconds */
-extern int upnpdurationtos(const std::string& dur);
+extern void timespec_addnanos(struct timespec *ts, long long nanos);
 
 }
 
-#endif /* _UPNPAVUTILS_HXX_INCLUDED_ */
+#endif /* _UPNPPUTILS_H_X_INCLUDED_ */
<< < 1 2 3 (Page 3 of 3)