separation cleanup

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

<< < 1 2 3 > >> (Page 2 of 3)
copied src/control/#mediaserver.hxx# -> libupnpp/control/mediaserver.hxx
copied src/control/avlastchg.cxx -> libupnpp/control/avlastchg.cxx
copied src/control/avlastchg.hxx -> libupnpp/control/avlastchg.hxx
copied src/control/avtransport.cxx -> libupnpp/control/avtransport.cxx
copied src/control/avtransport.hxx -> libupnpp/control/avtransport.hxx
copied src/control/cdircontent.cxx -> libupnpp/control/cdircontent.cxx
copied src/control/cdircontent.hxx -> libupnpp/device/device.hxx
copied src/control/cdirectory.cxx -> libupnpp/control/ohplaylist.cxx
copied src/control/cdirectory.hxx -> libupnpp/control/service.hxx
copied src/control/description.cxx -> libupnpp/control/description.cxx
copied src/control/description.hxx -> libupnpp/control/description.hxx
copied src/control/device.hxx -> libupnpp/control/device.hxx
copied src/control/discovery.cxx -> libupnpp/device/device.cxx
copied src/control/discovery.hxx -> libupnpp/control/discovery.hxx
copied src/control/httpdownload.cxx -> libupnpp/control/httpdownload.cxx
copied src/control/mediarenderer.cxx -> libupnpp/control/mediarenderer.cxx
copied src/control/mediarenderer.hxx -> libupnpp/control/mediarenderer.hxx
copied src/control/mediaserver.cxx -> libupnpp/control/ohplaylist.hxx
copied src/control/mediaserver.hxx -> libupnpp/ixmlwrap.hxx
copied src/control/ohplaylist.cxx -> libupnpp/upnpplib.cxx
copied src/control/ohplaylist.hxx -> libupnpp/control/renderingcontrol.hxx
copied src/control/ohproduct.cxx -> libupnpp/control/ohproduct.cxx
copied src/control/ohproduct.hxx -> libupnpp/control/ohproduct.hxx
copied src/control/renderingcontrol.cxx -> libupnpp/control/renderingcontrol.cxx
copied src/control/renderingcontrol.hxx -> libupnpp/ixmlwrap.cxx
src/control/#mediaserver.hxx# to libupnpp/control/mediaserver.hxx
--- a/src/control/#mediaserver.hxx#
+++ b/libupnpp/control/mediaserver.hxx
@@ -41,11 +41,9 @@
                                const std::string& friendlyName = "");
     static bool isMSDevice(const std::string& devicetype);
 
-    enum ServerKind {MSKIND_UNKNOWN, MSKIND_MINIM}:
-
 protected:
     CDSH m_cds;
-    KnownType m_serverKindknownType;
+
     static const std::string DType;
 };
 
src/control/cdircontent.hxx to libupnpp/device/device.hxx
--- a/src/control/cdircontent.hxx
+++ b/libupnpp/device/device.hxx
@@ -1,218 +1,189 @@
-/* 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 _UPNPDIRCONTENT_H_X_INCLUDED_
-#define _UPNPDIRCONTENT_H_X_INCLUDED_
+#ifndef _DEVICE_H_X_INCLUDED_
+#define _DEVICE_H_X_INCLUDED_
 
-#include <map>                          // for map, etc
-#include <sstream>                      // for operator<<, basic_ostream, etc
-#include <string>                       // for string, char_traits, etc
-#include <utility>                      // for pair
+#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/upnpavutils.hxx"     // for upnpdurationtos
+#include "libupnpp/ptmutex.hxx"         // for PTMutexInit
+#include "libupnpp/soaphelp.hxx"        // for SoapArgs, SoapData
 
-namespace UPnPClient {
+namespace UPnPP { class LibUPnP; }
+namespace UPnPProvider { class UpnpService; }
 
-/** 
- * UPnP resource. A resource describes one of the entities associated with 
- * a directory entry. This would be typically the audio file URI, and
- * its characteristics (sample rate etc.) as attributes, but there can
- * be several resources associated to one entry, for example for
- * multiple encoding formats.
+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;
+};
+
+/** Define a virtual interface to link libupnp operations to a device 
+ * implementation 
  */
-class UPnPResource {
+class UpnpDevice {
 public:
-    // Value
-    std::string m_uri;
-    // Attributes
-    std::map<std::string, std::string> m_props;
+    /** 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*);
 };
 
 /**
- * UPnP Media Server directory entry, converted from XML data.
- *
- * This is a dumb data holder class, a struct with helpers.
+ * 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 UPnPDirObject {
+class UpnpService {
 public:
-    enum ObjType {item, container};
-    // There are actually several kinds of containers:
-    // object.container.storageFolder, object.container.person,
-    // object.container.playlistContainer etc., but they all seem to
-    // behave the same as far as we're concerned. Otoh, musicTrack
-    // items are special to us, and so should playlists, but I've not
-    // seen one of the latter yet (servers seem to use containers for
-    // playlists).
-    enum ItemClass {audioItem_musicTrack, audioItem_playlist};
-
-    std::string m_id; // ObjectId
-    std::string m_pid; // Parent ObjectId
-    std::string m_title; // dc:title. Directory name for a container.
-    ObjType m_type; // item or container
-    ItemClass m_iclass;
-    // Properties as gathered from the XML document (album, artist, etc.),
-    // except for title which has a proper field.
-    // The map keys are the XML tag names
-    std::map<std::string, std::string> m_props;
-
-    // Resources: there may be several, for example for different
-    // audio formats of the same track, each with an URI and descriptor fields
-    std::vector<UPnPResource> m_resources;
-
-    /** Get named property
-     * @param property name (e.g. upnp:artist, upnp:album,
-     *     upnp:originalTrackNumber, upnp:genre). Use m_title instead
-     *     for dc:title.
-     * @param[out] value
-     * @return true if found.
-     */
-    bool getprop(const std::string& name, std::string& value) const
-    {
-        std::map<std::string, std::string>::const_iterator it =
-            m_props.find(name);
-        if (it == m_props.end())
-            return false;
-        value = it->second;
-        return true;
-    }
-
-    /** Get named property for resource 
-     * Field names: "bitrate", "duration" (H:mm:ss.ms), "nrAudioChannels",
-     * "protocolInfo", "sampleFrequency" (Hz), "size" (bytes)
-     */
-    bool getrprop(unsigned int ridx, const std::string& nm, std::string& val) 
-    const
-    {
-        if (ridx >= m_resources.size())
-            return false;
-        std::map<std::string, std::string>::const_iterator it =
-            m_resources[ridx].m_props.find(nm);
-        if (it == m_resources[ridx].m_props.end())
-            return false;
-        val = it->second;
-        return true;
-
-    }
-
-    /** Simplified interface to retrieving values: we don't distinguish
-     * between non-existing and empty, and we only use the first ressource
-     */
-    std::string f2s(const std::string& nm, bool isresfield) {
-        std::string val;
-        if (isresfield) {
-            getrprop(0, nm, val);
-        } else {
-            getprop(nm, val);
+    UpnpService(const std::string& stp,const std::string& sid, UpnpDevice *dev) 
+        : m_serviceType(stp), m_serviceId(sid)
+        {
+            dev->addService(this, sid);
         }
-        return val;
-    }
-
-    int getDurationSeconds(unsigned ridx = 0) const
-    {
-        std::string sdur;
-        if (!getrprop(ridx, "duration", sdur)) {
-            //?? Avoid returning 0, who knows...
-            return 1;
-        }
-        return UPnPP::upnpdurationtos(sdur);
-    }
+    virtual ~UpnpService() {}
 
     /** 
-     * Get a DIDL document suitable for sending to a mediaserver. Only
-     * works for items, not containers. The idea is that we may have
-     * missed useful stuff while parsing the data from the content
-     * directory, so we send the original if we can.
+     * 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.
      */
-    std::string getdidl() const;
+    virtual bool getEventData(bool all, std::vector<std::string>& names, 
+                              std::vector<std::string>& values) 
+        {
+            return true;
+        }
 
-    void clear()
-    {
-        m_id.clear();
-        m_pid.clear();
-        m_title.clear();
-        m_type = (ObjType)-1;
-        m_iclass = (ItemClass)-1;
-        m_props.clear();
-        m_resources.clear();
-        m_didlfrag.clear();
-    }
+    virtual const std::string& getServiceType() const
+        {
+            return m_serviceType;
+        }
+    virtual const std::string& getServiceId() const
+        {
+            return m_serviceId;
+        }
 
-    std::string dump() const
-    {
-        std::ostringstream os;
-        os << "UPnPDirObject: " << (m_type == item ? "item" : "container") << 
-            " id [" << m_id << "] pid [" << m_pid <<
-            "] title [" << m_title << "]" << std::endl;
-        os << "Properties: " << std::endl;
-        for (auto it = m_props.begin(); it != m_props.end(); it++) {
-            os << "[" << it->first << "]->[" << it->second << "] " 
-               << std::endl;
-        }
-        os << "Resources:" << std::endl;
-        for (auto it = m_resources.begin(); it != m_resources.end(); it++) {
-            os << "  Uri [" << it->m_uri << "]" << std::endl;
-            os << "  Resource attributes:" << std::endl;
-            for (auto it1 = it->m_props.begin(); 
-                 it1 != it->m_props.end(); it1++) {
-                os << "    [" << it1->first << "]->[" << it1->second << "] " 
-                   << std::endl;
-            }
-        }
-        os << std::endl;
-        return os.str();
-    }
-
-private:
-    friend class UPnPDirParser;
-    // didl text for element, sans header
-    std::string m_didlfrag;
+protected:
+    const std::string m_serviceType;
+    const std::string m_serviceId;
 };
 
-/**
- * Image of a MediaServer Directory Service container (directory),
- * possibly containing items and subordinate containers.
- */
-class UPnPDirContent {
-public:
-    std::vector<UPnPDirObject> m_containers;
-    std::vector<UPnPDirObject> m_items;
+} // End namespace UPnPProvider
 
-    void clear() 
-    { 
-        m_containers.clear();
-        m_items.clear();
-    }
-
-    /**
-     * Parse from DIDL-Lite XML data.
-     *
-     * Normally only used by ContentDirectory::readDir()
-     * This is cumulative: in general, the XML data is obtained in
-     * several documents corresponding to (offset,count) slices of the
-     * directory (container). parse() can be called repeatedly with
-     * the successive XML documents and will accumulate entries in the item
-     * and container vectors. This makes more sense if the different
-     * chunks are from the same container, but given that UPnP Ids are
-     * actually global, nothing really bad will happen if you mix
-     * up...
-     */
-    bool parse(const std::string& didltext);
-};
-
-} // namespace
-
-#endif /* _UPNPDIRCONTENT_H_X_INCLUDED_ */
+#endif /* _DEVICE_H_X_INCLUDED_ */
src/control/cdirectory.cxx to libupnpp/control/ohplaylist.cxx
--- a/src/control/cdirectory.cxx
+++ b/libupnpp/control/ohplaylist.cxx
@@ -1,381 +1,413 @@
-/* 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 "libupnpp/control/cdirectory.hxx"
-
-#include <sys/types.h>
-#include <regex.h>
-
-#include <upnp/upnp.h>                  // for UPNP_E_SUCCESS, etc
-#include <upnp/upnptools.h>             // for UpnpGetErrorMessage
-
-#include <functional>                   // for _Bind, bind, _1, _2
-#include <iostream>                     // for operator<<, basic_ostream, etc
-#include <set>                          // for set
-#include <string>                       // for string, operator<<, etc
+#include "libupnpp/control/ohplaylist.hxx"
+
+#include <expat_external.h>             // for XML_Char
+#include <netinet/in.h>                 // for ntohl
+#include <stdlib.h>                     // for atoi
+#include <string.h>                     // for strcmp
+#include <upnp/upnp.h>                  // for UPNP_E_BAD_RESPONSE, etc
+
+#include <functional>                   // for _Bind, bind, _1
+#include <ostream>                      // for endl, basic_ostream, etc
+#include <string>                       // for string, basic_string, etc
+#include <utility>                      // for pair
 #include <vector>                       // for vector
 
-#include "libupnpp/control/cdircontent.hxx"  // for UPnPDirContent
-#include "libupnpp/control/description.hxx"  // for UPnPDeviceDesc, etc
-#include "libupnpp/control/discovery.hxx"  // for UPnPDeviceDirectory, etc
-#include "libupnpp/log.hxx"             // for LOGDEB, LOGINF, LOGERR
-#include "libupnpp/soaphelp.hxx"        // for SoapEncodeInput, SoapArgs, etc
-#include "libupnpp/upnpp_p.hxx"         // for csvToStrings
+#include "libupnpp/base64.hxx"          // for base64_decode
+#include "libupnpp/control/cdircontent.hxx"  // for UPnPDirContent, etc
+#include "libupnpp/control/service.hxx"  // for VarEventReporter, Service
+#include "libupnpp/expatmm.hxx"         // for inputRefXMLParser
+#include "libupnpp/log.hxx"             // for LOGERR, LOGDEB1, LOGINF
+#include "libupnpp/soaphelp.hxx"        // for SoapDecodeOutput, etc
+#include "libupnpp/upnpp_p.hxx"         // for stringToBool
 
 using namespace std;
 using namespace std::placeholders;
+using namespace UPnPP;
 
 namespace UPnPClient {
 
-// The service type string for Content Directories:
-const string ContentDirectory::SType("urn:schemas-upnp-org:service:ContentDirectory:1");
-
-class SimpleRegexp {
-public:
-    SimpleRegexp(const string& exp, int flags) : m_ok(false) {
-        if (regcomp(&m_expr, exp.c_str(), flags) == 0) {
-            m_ok = true;
-        }
-    }
-    ~SimpleRegexp() {
-        regfree(&m_expr);
-    }
-    bool simpleMatch(const string& val) const {
-        if (!ok())
-            return false;
-        if (regexec(&m_expr, val.c_str(), 0, 0, 0) == 0) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-    bool operator() (const string& val) const {
-        return simpleMatch(val);
-    }
-
-    bool ok() const {return m_ok;}
-private:
-    bool m_ok;
-    regex_t m_expr;
-};
-
-/*
-  manufacturer: Bubblesoft model BubbleUPnP Media Server
-  manufacturer: Justin Maggard model Windows Media Connect compatible (MiniDLNA)
-  manufacturer: minimserver.com model MinimServer
-  manufacturer: PacketVideo model TwonkyMedia Server
-  manufacturer: ? model MediaTomb
-*/
-static const SimpleRegexp bubble_rx("bubble", REG_ICASE|REG_NOSUB);
-static const SimpleRegexp mediatomb_rx("mediatomb", REG_ICASE|REG_NOSUB);
-static const SimpleRegexp minidlna_rx("minidlna", REG_ICASE|REG_NOSUB);
-static const SimpleRegexp minim_rx("minim", REG_ICASE|REG_NOSUB);
-static const SimpleRegexp twonky_rx("twonky", REG_ICASE|REG_NOSUB);
-
-ContentDirectory::ContentDirectory(const UPnPDeviceDesc& device,
-                                   const UPnPServiceDesc& service)
-    : Service(device, service), m_rdreqcnt(200), m_serviceKind(CDSKIND_UNKNOWN)
-{
-    LOGERR("ContentDirectory::ContentDirectory: manufacturer: " << 
-           m_manufacturer << " model " << m_modelName << endl);
-
-    if (bubble_rx(m_modelName)) {
-        m_serviceKind = CDSKIND_BUBBLE;
-        LOGDEB1("ContentDirectory::ContentDirectory: BUBBLE" << endl);
-    } else if (mediatomb_rx(m_modelName)) {
-        // Readdir by 200 entries is good for most, but MediaTomb likes
-        // them really big. Actually 1000 is better but I don't dare
-        m_rdreqcnt = 500;
-        m_serviceKind = CDSKIND_MEDIATOMB;
-        LOGDEB1("ContentDirectory::ContentDirectory: MEDIATOMB" << endl);
-    } else if (minidlna_rx(m_modelName)) {
-        m_serviceKind = CDSKIND_MINIDLNA;
-        LOGDEB1("ContentDirectory::ContentDirectory: MINIDLNA" << endl);
-    } else if (minim_rx(m_modelName)) {
-        m_serviceKind = CDSKIND_MINIM;
-        LOGDEB1("ContentDirectory::ContentDirectory: MINIM" << endl);
-    } else if (twonky_rx(m_modelName)) {
-        m_serviceKind = CDSKIND_TWONKY;
-        LOGDEB1("ContentDirectory::ContentDirectory: TWONKY" << endl);
-    } 
-    registerCallback();
-}
-
-// We don't include a version in comparisons, as we are satisfied with
-// version 1
-bool ContentDirectory::isCDService(const string& st)
+const string OHPlaylist::SType("urn:av-openhome-org:service:Playlist:1");
+
+// Check serviceType string (while walking the descriptions. We don't
+// include a version in comparisons, as we are satisfied with version1
+bool OHPlaylist::isOHPlService(const string& st)
 {
     const string::size_type sz(SType.size()-2);
     return !SType.compare(0, sz, st, 0, sz);
 }
 
-static bool DSAccum(vector<CDSH>* out,
-                    const UPnPDeviceDesc& device, 
-                    const UPnPServiceDesc& service)
-{
-    if (ContentDirectory::isCDService(service.serviceType)) {
-        out->push_back(CDSH(new ContentDirectory(device, service)));
-    }
-    return true;
-}
-
-bool ContentDirectory::getServices(vector<CDSH>& vds)
-{
-    //LOGDEB("UPnPDeviceDirectory::getDirServices" << endl);
-    UPnPDeviceDirectory::Visitor visitor = bind(DSAccum, &vds, _1, _2);
-    UPnPDeviceDirectory::getTheDir()->traverse(visitor);
-    return !vds.empty();
-}
-
-// Get server by friendly name. 
-bool ContentDirectory::getServerByName(const string& fname, CDSH& server)
-{
-    UPnPDeviceDesc ddesc;
-    bool found = UPnPDeviceDirectory::getTheDir()->getDevByFName(fname, ddesc);
-    if (!found)
-        return false;
-
-    found = false;
-    for (auto it = ddesc.services.begin(); it != ddesc.services.end(); it++) {
-        if (isCDService(it->serviceType)) {
-            server = CDSH(new ContentDirectory(ddesc, *it));
-            found = true;
-            break;
+static int stringToTpState(const string& value, OHPlaylist::TPState *tpp)
+{
+    if (!value.compare("Buffering")) {
+        *tpp = OHPlaylist::TPS_Buffering;
+        return 0;
+    } else if (!value.compare("Paused")) {
+        *tpp = OHPlaylist::TPS_Paused;
+        return 0;
+    } else if (!value.compare("Playing")) {
+        *tpp = OHPlaylist::TPS_Playing;
+        return 0;
+    } else if (!value.compare("Stopped")) {
+        *tpp = OHPlaylist::TPS_Stopped;
+        return 0;
+    }
+    *tpp = OHPlaylist::TPS_Unknown;
+    return UPNP_E_BAD_RESPONSE;
+}
+
+// Translate IdArray: base64-encoded array of binary msb 32bits integers
+static void idArrayToVec(const string& _data, vector<int> *ids)
+{    
+    string data = base64_decode(_data);
+    const char *cp = data.c_str();
+    while (cp - data.c_str() <= int(data.size()) - 4) {
+        unsigned int *ip = (unsigned int *)cp;
+        ids->push_back(ntohl(*ip));
+        cp += 4;
+    }
+}
+
+void OHPlaylist::evtCallback(
+    const std::unordered_map<std::string, std::string>& props)
+{
+    LOGDEB1("OHPlaylist::evtCallback: m_reporter: " << m_reporter << endl);
+    for (auto it = props.begin(); it != props.end(); it++) {
+        if (!m_reporter) {
+            LOGDEB1("OHPlaylist::evtCallback: " << it->first << " -> " 
+                    << it->second << endl);
+            continue;
         }
-    }
-    return found;
-}
-
-#if 0
-static int asyncReaddirCB(Upnp_EventType et, void *vev, void *cookie)
-{
-    LOGDEB("asyncReaddirCB: " << LibUPnP::evTypeAsString(et) << endl);
-    struct Upnp_Action_Complete *act = (struct Upnp_Action_Complete*)vev;
-
-    LOGDEB("asyncReaddirCB: errcode " << act->ErrCode << 
-           " cturl " <<  UpnpString_get_String(act->CtrlUrl) << 
-           " actionrequest " << endl << 
-           ixmlwPrintDoc(act->ActionRequest) << endl <<
-           " actionresult " << ixmlwPrintDoc(act->ActionResult) << endl);
-    return 0;
-}
-    int ret = 
-        UpnpSendActionAsync(hdl, m_actionURL.c_str(), m_serviceType.c_str(),
-        0 /*devUDN*/, request, asyncReaddirCB, 0);
-    sleep(10);
-    return -1;
-#endif
-
-void ContentDirectory::evtCallback(const unordered_map<string, string>&)
-{
-}
-
-void ContentDirectory::registerCallback()
-{
-    LOGDEB("ContentDirectory::registerCallback"<< endl);
-    Service::registerCallback(bind(&ContentDirectory::evtCallback, 
-                                   this, _1));
-}
-
-int ContentDirectory::readDirSlice(const string& objectId, int offset,
-                                          int count, UPnPDirContent& dirbuf,
-                                          int *didreadp, int *totalp)
-{
-    LOGDEB("CDService::readDirSlice: objId [" << objectId << "] offset " << 
-           offset << " count " << count << endl);
-
-    // Create request
-    // Some devices require an empty SortCriteria, else bad params
-    SoapData args(m_serviceType, "Browse");
-    args("ObjectID", objectId)
-        ("BrowseFlag", "BrowseDirectChildren")
-        ("Filter", "*")
-        ("SortCriteria", "")
-        ("StartingIndex", SoapHelp::i2s(offset))
-        ("RequestedCount", SoapHelp::i2s(count));
-
-    SoapArgs data;
-    int ret = runAction(args, data);
-    if (ret != UPNP_E_SUCCESS) {
-        return ret;
-    }
-    int didread;
-    string tbuf;
-    if (!data.getInt("NumberReturned", &didread) ||
-        !data.getInt("TotalMatches", totalp) ||
-        !data.getString("Result", &tbuf)) {
-        LOGERR("CDService::readDir: missing elts in response" << endl);
-        return UPNP_E_BAD_RESPONSE;
-    }
-
-    if (didread <= 0) {
-        LOGINF("CDService::readDir: got -1 or 0 entries" << endl);
-        return UPNP_E_BAD_RESPONSE;
-    }
-
-#if 0
-    cerr << "CDService::readDirSlice: count " << count <<
-        " offset " << offset <<
-        " total " << *totalp << endl;
-    cerr << " result " << tbuf << endl;
-#endif
-
-    dirbuf.parse(tbuf);
-    *didreadp = didread;
-
-    return UPNP_E_SUCCESS;
-}
-
-int ContentDirectory::readDir(const string& objectId,
-                                     UPnPDirContent& dirbuf)
-{
-    LOGDEB("CDService::readDir: url [" << m_actionURL << "] type [" <<
-           m_serviceType << "] udn [" << m_deviceId << "] objId [" <<
-           objectId << endl);
-
-    int offset = 0;
-    int total = 1000;// Updated on first read.
-
-    while (offset < total) {
-        int count;
-        int error = readDirSlice(objectId, offset, m_rdreqcnt, dirbuf,
-                                 &count, &total);
-        if (error != UPNP_E_SUCCESS)
-            return error;
-
-        offset += count;
-    }
-
-    return UPNP_E_SUCCESS;
-}
-
-int ContentDirectory::search(const string& objectId,
-                                    const string& ss,
-                                    UPnPDirContent& dirbuf)
-{
-    LOGDEB("CDService::search: url [" << m_actionURL << "] type [" << 
-           m_serviceType << "] udn [" << m_deviceId << "] objid [" << 
-           objectId <<  "] search [" << ss << "]" << endl);
-
-    int offset = 0;
-    int total = 1000;// Updated on first read.
-
-    while (offset < total) {
-        // Create request
-        SoapData args(m_serviceType, "Search");
-        args("ContainerID", objectId)
-            ("SearchCriteria", ss)
-            ("Filter", "*")
-            ("SortCriteria", "")
-            ("StartingIndex", SoapHelp::i2s(offset))
-            ("RequestedCount", "10"); 
-
-        SoapArgs data;
-        int ret = runAction(args, data);
-
-        if (ret != UPNP_E_SUCCESS) {
-            LOGINF("CDService::search: UpnpSendAction failed: " <<
-                   UpnpGetErrorMessage(ret) << endl);
-            return ret;
+
+        if (!it->first.compare("TransportState")) {
+            TPState tp;
+            stringToTpState(it->second, &tp);
+            m_reporter->changed(it->first.c_str(), int(tp));
+
+        } else if (!it->first.compare("ProtocolInfo")) {
+            m_reporter->changed(it->first.c_str(), 
+                                it->second.c_str());
+
+        } else if (!it->first.compare("Repeat") ||
+                   !it->first.compare("Shuffle")) {
+            bool val = false;
+            stringToBool(it->second, &val);
+            m_reporter->changed(it->first.c_str(), val ? 1 : 0);
+
+        } else if (!it->first.compare("Id") ||
+                   !it->first.compare("TracksMax")) {
+            m_reporter->changed(it->first.c_str(),
+                                atoi(it->second.c_str()));
+            
+        } else if (!it->first.compare("IdArray")) {
+            // Decode IdArray. See how we call the client
+            vector<int> v;
+            idArrayToVec(it->second, &v);
+            m_reporter->changed(it->first.c_str(), v);
+
+        } else {
+            LOGERR("OHPlaylist event: unknown variable: name [" <<
+                   it->first << "] value [" << it->second << endl);
+                m_reporter->changed(it->first.c_str(), it->second.c_str());
         }
-
-        int count = -1;
-        string tbuf;
-        if (!data.getInt("NumberReturned", &count) ||
-            !data.getInt("TotalMatches", &total) ||
-            !data.getString("Result", &tbuf)) {
-            LOGERR("CDService::search: missing elts in response" << endl);
-            return UPNP_E_BAD_RESPONSE;
+    }
+}
+
+void OHPlaylist::registerCallback()
+{
+    Service::registerCallback(bind(&OHPlaylist::evtCallback, this, _1));
+}
+
+int OHPlaylist::play()
+{
+    return runTrivialAction("Play");
+}
+int OHPlaylist::pause()
+{
+    return runTrivialAction("Pause");
+}
+int OHPlaylist::stop()
+{
+    return runTrivialAction("Stop");
+}
+int OHPlaylist::next()
+{
+    return runTrivialAction("Next");
+}
+int OHPlaylist::previous()
+{
+    return runTrivialAction("Previous");
+}
+int OHPlaylist::setRepeat(bool onoff)
+{
+    return runSimpleAction("SetRepeat", "Value", onoff);
+}
+int OHPlaylist::repeat(bool *on)
+{
+    return runSimpleGet("Repeat", "Value", on);
+}
+int OHPlaylist::setShuffle(bool onoff)
+{
+    return runSimpleAction("SetShuffle", "Value", onoff);
+}
+int OHPlaylist::shuffle(bool *on)
+{
+    return runSimpleGet("Shuffle", "Value", on);
+}
+int OHPlaylist::seekSecondAbsolute(int value)
+{
+    return runSimpleAction("SeekSecondAbsolute", "Value", value);
+}
+int OHPlaylist::seekSecondRelative(int value)
+{
+    return runSimpleAction("SeekSecondRelative", "Value", value);
+}
+int OHPlaylist::seekId(int value)
+{
+    return runSimpleAction("SeekId", "Value", value);
+}
+int OHPlaylist::seekIndex(int value)
+{
+    return runSimpleAction("SeekIndex", "Value", value);
+}
+
+int OHPlaylist::transportState(TPState* tpp)
+{
+    string value;
+    int ret;
+
+    if ((ret = runSimpleGet("TransportState", "Value", &value)))
+        return ret;
+
+    return stringToTpState(value, tpp);
+}
+
+int OHPlaylist::id(int *value)
+{
+    return runSimpleGet("Id", "Value", value);
+}
+
+int OHPlaylist::read(int id, std::string* urip, UPnPDirObject *dirent)
+{
+    SoapEncodeInput args(m_serviceType, "Read");
+    args("Id", SoapHelp::i2s(id));
+    SoapDecodeOutput data;
+    int ret = runAction(args, data);
+    if (ret != UPNP_E_SUCCESS) {
+        return ret;
+    }
+    if (!data.get("Uri", urip)) {
+        LOGERR("OHPlaylist::Read: missing Uri in response" << endl);
+        return UPNP_E_BAD_RESPONSE;
+    }
+    string didl;
+    if (!data.get("Metadata", &didl)) {
+        LOGERR("OHPlaylist::Read: missing Uri in response" << endl);
+        return UPNP_E_BAD_RESPONSE;
+    }
+    didl = SoapHelp::xmlUnquote(didl);
+
+    UPnPDirContent dir;
+    if (!dir.parse(didl)) {
+        LOGERR("OHPlaylist::Read: didl parse failed: " << didl << endl);
+        return UPNP_E_BAD_RESPONSE;
+    }
+    if (dir.m_items.size() != 1) {
+        LOGERR("OHPlaylist::Read: " << dir.m_items.size() << " in response!" <<
+               endl);
+        return UPNP_E_BAD_RESPONSE;
+    }
+    *dirent = dir.m_items[0];
+    return 0;
+}
+
+// Tracklist format
+// <TrackList>
+//   <Entry>
+//     <Id>10</Id>
+//     <Uri>http://blabla</Uri>
+//     <Metadata>(xmlencoded didl)</Metadata>
+//   </Entry>
+// </TrackList>
+
+
+class OHTrackListParser : public inputRefXMLParser {
+public:
+    OHTrackListParser(const string& input, 
+                      vector<OHPlaylist::TrackListEntry>* vp)
+        : inputRefXMLParser(input), m_v(vp)
+        {
+            //LOGDEB("OHTrackListParser: input: " << input << endl);
         }
-        if (count <=  0) {
-            LOGINF("CDService::search: got -1 or 0 entries" << endl);
-            return count < 0 ? UPNP_E_BAD_RESPONSE : UPNP_E_SUCCESS;
+
+protected:
+    virtual void StartElement(const XML_Char *name, const XML_Char **) {
+        m_path.push_back(name);
+    }
+    virtual void EndElement(const XML_Char *name) {
+        if (!strcmp(name, "Entry")) {
+            UPnPDirContent dir;
+            if (!dir.parse(m_tdidl)) {
+                LOGERR("OHPlaylist::ReadList: didl parse failed: " 
+                       << m_tdidl << endl);
+                return;
+            }
+            if (dir.m_items.size() != 1) {
+                LOGERR("OHPlaylist::ReadList: " << dir.m_items.size() 
+                       << " in response!" << endl);
+                return;
+            }
+            m_tt.dirent = dir.m_items[0];
+            m_v->push_back(m_tt);
+            m_tt.clear();
+            m_tdidl.clear();
         }
-        offset += count;
-
-        dirbuf.parse(tbuf);
-    }
-
-    return UPNP_E_SUCCESS;
-}
-
-int ContentDirectory::getSearchCapabilities(set<string>& result)
-{
-    LOGDEB("CDService::getSearchCapabilities:" << endl);
-
-    SoapData args(m_serviceType, "GetSearchCapabilities");
-    SoapArgs data;
-    int ret = runAction(args, data);
-    if (ret != UPNP_E_SUCCESS) {
-        LOGINF("CDService::getSearchCapa: UpnpSendAction failed: " << 
-               UpnpGetErrorMessage(ret) << endl);
-        return ret;
-    }
-    string tbuf;
-    if (!data.getString("SearchCaps", &tbuf)) {
-        LOGERR("CDService::getSearchCaps: missing Result in response" << endl);
-        cerr << tbuf << endl;
-        return UPNP_E_BAD_RESPONSE;
-    }
-
-    result.clear();
-    if (!tbuf.compare("*")) {
-        result.insert(result.end(), "*");
-    } else if (!tbuf.empty()) {
-        if (!csvToStrings(tbuf, result)) {
-            return UPNP_E_BAD_RESPONSE;
-        }
-    }
-
-    return UPNP_E_SUCCESS;
-}
-
-int ContentDirectory::getMetadata(const string& objectId,
-                                         UPnPDirContent& dirbuf)
-{
-    LOGDEB("CDService::getMetadata: url [" << m_actionURL << "] type [" <<
-           m_serviceType << "] udn [" << m_deviceId << "] objId [" <<
-           objectId << "]" << endl);
-
-    SoapData args(m_serviceType, "Browse");
-    SoapArgs data;
-    args("ObjectID", objectId)
-        ("BrowseFlag", "BrowseMetadata")
-        ("Filter", "*")
-        ("SortCriteria", "")
-        ("StartingIndex", "0")
-        ("RequestedCount", "1");
-    int ret = runAction(args, data);
-    if (ret != UPNP_E_SUCCESS) {
-        LOGINF("CDService::getmetadata: UpnpSendAction failed: " << 
-               UpnpGetErrorMessage(ret) << endl);
-        return ret;
-    }
-    string tbuf;
-    if (!data.getString("Result", &tbuf)) {
-        LOGERR("CDService::getmetadata: missing Result in response" << endl);
-        return UPNP_E_BAD_RESPONSE;
-    }
-
-    if (dirbuf.parse(tbuf))
-        return UPNP_E_SUCCESS;
-    else
-        return UPNP_E_BAD_RESPONSE;
-}
-
-} // namespace UPnPClient
+        m_path.pop_back();
+    }
+    virtual void CharacterData(const XML_Char *s, int len) {
+        if (s == 0 || *s == 0)
+            return;
+        string str(s, len);
+        if (!m_path.back().compare("Id"))
+            m_tt.id = atoi(str.c_str());
+        else if (!m_path.back().compare("Uri"))
+            m_tt.url = str;
+        else if (!m_path.back().compare("Metadata"))
+            m_tdidl += str;
+    }
+
+private:
+    vector<OHPlaylist::TrackListEntry>* m_v;
+    std::vector<std::string> m_path;
+    OHPlaylist::TrackListEntry m_tt;
+    string m_tdidl;
+};
+
+int OHPlaylist::readList(const std::vector<int>& ids, 
+                         vector<TrackListEntry>* entsp)
+{
+    string idsparam;
+    for (auto it = ids.begin(); it != ids.end(); it++) {
+        idsparam += SoapHelp::i2s(*it) + " ";
+    }
+    entsp->clear();
+
+    SoapEncodeInput args(m_serviceType, "ReadList");
+    args("IdList", idsparam);
+    SoapDecodeOutput data;
+    int ret = runAction(args, data);
+    if (ret != UPNP_E_SUCCESS) {
+        return ret;
+    }
+    string xml;
+    if (!data.get("TrackList", &xml)) {
+        LOGERR("OHPlaylist::readlist: missing TrackList in response" << endl);
+        return UPNP_E_BAD_RESPONSE;
+    }
+    OHTrackListParser mparser(xml, entsp);
+    if (!mparser.Parse())
+        return UPNP_E_BAD_RESPONSE;
+    return 0;
+}
+
+int OHPlaylist::insert(int afterid, const string& uri, const string& didl, 
+                       int *nid)
+{
+    SoapEncodeInput args(m_serviceType, "Insert");
+    args("AfterId", SoapHelp::i2s(afterid))
+        ("Uri", uri)
+        ("Metadata", didl);
+    SoapDecodeOutput data;
+    int ret = runAction(args, data);
+    if (ret != UPNP_E_SUCCESS) {
+        return ret;
+    }
+    if (!data.get("NewId", nid)) {
+        LOGERR("OHPlaylist::insert: missing Newid in response" << endl);
+        return UPNP_E_BAD_RESPONSE;
+    }
+    return 0;
+}
+
+
+int OHPlaylist::deleteId(int value)
+{
+    return runSimpleAction("DeleteId", "Value", value);
+}
+int OHPlaylist::deleteAll()
+{
+    return runTrivialAction("DeleteAll");
+}
+int OHPlaylist::tracksMax(int *valuep)
+{
+    return runSimpleGet("TracksMax", "Value", valuep);
+}
+
+int OHPlaylist::idArray(vector<int> *ids, int *tokp)
+{
+    SoapEncodeInput args(m_serviceType, "IdArray");
+    SoapDecodeOutput data;
+    int ret = runAction(args, data);
+    if (ret != UPNP_E_SUCCESS) {
+        return ret;
+    }
+    if (!data.get("Token", tokp)) {
+        LOGERR("OHPlaylist::idArray: missing Token in response" << endl);
+        return UPNP_E_BAD_RESPONSE;
+    }
+    string arraydata;
+    if (!data.get("Array", &arraydata)) {
+        LOGINF("OHPlaylist::idArray: missing Array in response" << endl);
+        // We get this for an empty array ? This would need to be investigated
+    }
+    idArrayToVec(arraydata, ids);
+    return 0;
+}
+
+int OHPlaylist::idArrayChanged(int token, bool *changed)
+{
+    SoapEncodeInput args(m_serviceType, "IdArrayChanged");
+    args("Token", SoapHelp::i2s(token));
+    SoapDecodeOutput data;
+    int ret = runAction(args, data);
+    if (ret != UPNP_E_SUCCESS) {
+        return ret;
+    }
+    if (!data.get("Value", changed)) {
+        LOGERR("OHPlaylist::idArrayChanged: missing Value in response" << endl);
+        return UPNP_E_BAD_RESPONSE;
+    }
+    return 0;
+}
+
+int OHPlaylist::protocolInfo(std::string *proto)
+{
+    SoapEncodeInput args(m_serviceType, "ProtocolInfo");
+    SoapDecodeOutput data;
+    int ret = runAction(args, data);
+    if (ret != UPNP_E_SUCCESS) {
+        return ret;
+    }
+    if (!data.get("Value", proto)) {
+        LOGERR("OHPlaylist::protocolInfo: missing Value in response" << endl);
+        return UPNP_E_BAD_RESPONSE;
+    }
+    return 0;
+}
+
+} // End namespace UPnPClient
src/control/cdirectory.hxx to libupnpp/control/service.hxx
--- a/src/control/cdirectory.hxx
+++ b/libupnpp/control/service.hxx
@@ -1,4 +1,4 @@
-/* Copyright (C) 2013 J.F.Dockes
+/* 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
@@ -14,140 +14,156 @@
  *       Free Software Foundation, Inc.,
  *       59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
-#ifndef _UPNPDIR_HXX_INCLUDED_
-#define _UPNPDIR_HXX_INCLUDED_
+#ifndef _SERVICE_H_X_INCLUDED_
+#define _SERVICE_H_X_INCLUDED_
 
-#include <memory>                       // for shared_ptr
-#include <set>                          // for set
-#include <string>                       // for string
+#include <upnp/upnp.h>                  // for UPNP_E_BAD_RESPONSE, 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 "libupnpp/control/service.hxx"  // for Service
+#include "libupnpp/control/cdircontent.hxx"  // for UPnPDirObject
+#include "libupnpp/log.hxx"             // for LOGERR
+#include "libupnpp/soaphelp.hxx"        // for SoapDecodeOutput, etc
 
-namespace UPnPClient { class ContentDirectory; }  // lines 30-30
 namespace UPnPClient { class UPnPDeviceDesc; }
-namespace UPnPClient { class UPnPDirContent; }
 namespace UPnPClient { class UPnPServiceDesc; }
+
+using namespace UPnPP;
 
 namespace UPnPClient {
 
-typedef std::shared_ptr<ContentDirectory> CDSH;
+/** 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*/) {};
+};
 
-/**
- * Content Directory Service client class.
- *
- * This stores identity data from a directory service
- * and the device it belongs to, and has methods to query
- * the directory, using libupnp for handling the UPnP protocols.
- *
- * Note: m_rdreqcnt: number of entries requested per directory read.
- * 0 means all entries. The device can still return less entries than
- * requested, depending on its own limits. In general it's not optimal
- * becauses it triggers issues, and is sometimes actually slower, e.g. on
- * a D-Link NAS 327
- *
- * The value chosen may affect by the UpnpSetMaxContentLength
- * (2000*1024) done during initialization, but this should be ample
- */
-class ContentDirectory : public Service {
+typedef 
+std::function<void (const std::unordered_map<std::string, std::string>&)> 
+evtCBFunc;
+
+class Service {
 public:
-
-    /** Construct by copying data from device and service objects. */
-    ContentDirectory(const UPnPDeviceDesc& device,
-                     const UPnPServiceDesc& service);
+    /** Construct by copying data from device and service objects.
+     */
+    Service(const UPnPDeviceDesc& device,
+            const UPnPServiceDesc& service, bool doSubscribe = true);
 
     /** An empty one */
-    ContentDirectory() : m_rdreqcnt(200), m_serviceKind(CDSKIND_UNKNOWN) {}
+    Service() : m_reporter(0) {}
 
-    enum ServiceKind {CDSKIND_UNKNOWN, CDSKIND_BUBBLE, CDSKIND_MEDIATOMB,
-                      CDSKIND_MINIDLNA, CDSKIND_MINIM, CDSKIND_TWONKY};
+    virtual ~Service();
 
-    ServiceKind getKind() {return m_serviceKind;}
+    /** Retrieve my root device "friendly name". */
+    std::string getFriendlyName() const {return m_friendlyName;}
 
-    /** Test service type from discovery message */
-    static bool isCDService(const std::string& st);
+    /** Return my root device id */
+    std::string getDeviceId() const {return m_deviceId;}
 
-    /** Retrieve the directory services currently seen on the network */
-    static bool getServices(std::vector<CDSH>&);
+    virtual int runAction(const SoapEncodeInput& args, SoapDecodeOutput& data);
 
-    /** Retrieve specific service designated by its friendlyName */
-    static bool getServerByName(const std::string& friendlyName, 
-                                CDSH& server);
-
-    /** Read a full container's children list 
-     *
-     * @param objectId the UPnP object Id for the container. Root has Id "0"
-     * @param[out] dirbuf stores the entries we read.
-     * @return UPNP_E_SUCCESS for success, else libupnp error code.
-     */
-    int readDir(const std::string& objectId, UPnPDirContent& dirbuf);
-
-    /** Read a partial slice of a container's children list
-     *
-     * The entries read are concatenated to the input dirbuf.
-     *
-     * @param objectId the UPnP object Id for the container. Root has Id "0"
-     * @param offset the offset of the first entry to read
-     * @param count the maximum number of entries to read
-     * @param[out] dirbuf a place to store the entries we read. They are 
-     *        appended to the existing ones.
-     * @param[out] didread number of entries actually read.
-     * @param[out] total total number of children.
-     * @return UPNP_E_SUCCESS for success, else libupnp error code.
-     */
-    int readDirSlice(const std::string& objectId, int offset,
-                     int count, UPnPDirContent& dirbuf,
-                     int *didread, int *total);
-
-    int goodSliceSize()
+    virtual VarEventReporter *getReporter()
     {
-        return m_rdreqcnt;
+        return m_reporter;
     }
 
-    /** Search the content directory service.
-     *
-     * @param objectId the UPnP object Id under which the search
-     * should be done. Not all servers actually support this below
-     * root. Root has Id "0"
-     * @param searchstring an UPnP searchcriteria string. Check the
-     * UPnP document: UPnP-av-ContentDirectory-v1-Service-20020625.pdf
-     * section 2.5.5. Maybe we'll provide an easier way some day...
-     * @param[out] dirbuf stores the entries we read.
-     * @return UPNP_E_SUCCESS for success, else libupnp error code.
-     */
-    int search(const std::string& objectId, const std::string& searchstring,
-               UPnPDirContent& dirbuf);
+    virtual void installReporter(VarEventReporter* reporter)
+    {
+        m_reporter = reporter;
+    }
 
-    /** Read metadata for a given node.
-     *
-     * @param objectId the UPnP object Id. Root has Id "0"
-     * @param[out] dirbuf stores the entries we read. At most one entry will be
-     *   returned.
-     * @return UPNP_E_SUCCESS for success, else libupnp error code.
-     */
-    int getMetadata(const std::string& objectId, UPnPDirContent& dirbuf);
-
-    /** Retrieve search capabilities
-     *
-     * @param[out] result an empty vector: no search, or a single '*' element:
-     *     any tag can be used in a search, or a list of usable tag names.
-     * @return UPNP_E_SUCCESS for success, else libupnp error code.
-     */
-    int getSearchCapabilities(std::set<std::string>& result);
+    // 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:
-    /* My service type string */
-    static const std::string SType;
+
+    /** 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:
-    int m_rdreqcnt; // Slice size to use when reading
-    ServiceKind m_serviceKind;
+    /** 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();
 
-    void evtCallback(const std::unordered_map<std::string, std::string>&);
-    void registerCallback();
+    Upnp_SID    m_SID; /* Subscription Id */
 };
 
-}
+} // namespace UPnPClient
 
-#endif /* _UPNPDIR_HXX_INCLUDED_ */
+#endif /* _SERVICE_H_X_INCLUDED_ */
src/control/discovery.cxx to libupnpp/device/device.cxx
--- a/src/control/discovery.cxx
+++ b/libupnpp/device/device.cxx
@@ -1,479 +1,450 @@
-/* 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 "discovery.hxx"
-
-#include <pthread.h>                    // for pthread_cond_broadcast, etc
-#include <sched.h>                      // for sched_yield
-#include <stdlib.h>                     // for free
+#include "device.hxx"
+
+#include <errno.h>                      // for ETIMEDOUT, errno
 #include <sys/time.h>                   // for CLOCK_REALTIME
-#include <unistd.h>                     // for sleep
-#include <upnp/upnp.h>                  // for Upnp_Discovery, etc
-
-#include <functional>                   // for _Bind, bind, function, _1, etc
-#include <iostream>                     // for operator<<, basic_ostream, etc
-#include <map>                          // for _Rb_tree_iterator, map, etc
+#include <time.h>                       // for timespec, clock_gettime
+
+#include <iostream>                     // for endl, operator<<, etc
 #include <utility>                      // for pair
-#include <vector>                       // for vector
-#include <unordered_set>
-
-#include "description.hxx"              // for UPnPDeviceDesc, etc
-
-#include "libupnpp/log.hxx"             // for LOGDEB1, LOGERR, LOGDEB
-#include "libupnpp/ptmutex.hxx"         // for PTMutexLocker, PTMutexInit
+
+#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 "libupnpp/workqueue.hxx"       // for WorkQueue
-#include "libupnpp/control/httpdownload.hxx"
+
+#include "vdir.hxx"                     // for VirtualDir
 
 using namespace std;
-using namespace std::placeholders;
 using namespace UPnPP;
 
-namespace UPnPClient {
-
-static UPnPDeviceDirectory *theDevDir;
-
-//#undef LOCAL_LOGINC
-//#define LOCAL_LOGINC 3
-
-static string cluDiscoveryToStr(const struct Upnp_Discovery *disco)
-{
-    stringstream ss;
-    ss << "ErrCode: " << disco->ErrCode << endl;
-    ss << "Expires: " << disco->Expires << endl;
-    ss << "DeviceId: " << disco->DeviceId << endl;
-    ss << "DeviceType: " << disco->DeviceType << endl;
-    ss << "ServiceType: " << disco->ServiceType << endl;
-    ss << "ServiceVer: " << disco->ServiceVer    << endl;
-    ss << "Location: " << disco->Location << endl;
-    ss << "Os: " << disco->Os << endl;
-    ss << "Date: " << disco->Date << endl;
-    ss << "Ext: " << disco->Ext << endl;
-
-    /** The host address of the device responding to the search. */
-    // struct sockaddr_storage DestAddr;
-    return ss.str();
-}
-
-// Each appropriate discovery event (executing in a libupnp thread
-// context) queues the following task object for processing by the
-// discovery thread.
-class DiscoveredTask {
-public:
-    DiscoveredTask(bool _alive, const struct Upnp_Discovery *disco)
-        : alive(_alive), url(disco->Location), deviceId(disco->DeviceId),
-          expires(disco->Expires)
-        {}
-
-    bool alive;
-    string url;
-    string description;
-    string deviceId;
-    int expires; // Seconds valid
-};
-
-// The workqueue on which callbacks from libupnp (cluCallBack()) queue
-// discovered object descriptors for processing by our dedicated
-// thread.
-static WorkQueue<DiscoveredTask*> discoveredQueue("DiscoveredQueue");
-static unordered_set<string> o_downloading;
-static PTMutexInit o_downloading_mutex;
-
-// This gets called in a libupnp thread context for all asynchronous
-// events which we asked for.
-// Example: ContentDirectories appearing and disappearing from the network
-// We queue a task for our worker thread(s)
-// We can get called by several threads.
-static int cluCallBack(Upnp_EventType et, void* evp, void*)
-{
-    LOGDEB1("discovery:cluCallBack: " << LibUPnP::evTypeAsString(et) << endl);
-
+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_DISCOVERY_SEARCH_RESULT:
-    case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
+    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;
     {
-        struct Upnp_Discovery *disco = (struct Upnp_Discovery *)evp;
-        // Devices send multiple messages for themselves, their subdevices and 
-        // services. AFAIK they all point to the same description.xml document,
-        // which has all the interesting data. So let's try to only process
-        // one message per device: the one which probably correspond to the 
-        // upnp "root device" message and has empty service and device types:
-        if (!disco->DeviceType[0] && !disco->ServiceType[0]) {
-            LOGDEB1("discovery:cllb:ALIVE: " << cluDiscoveryToStr(disco) 
-                   << endl);
-            // Device signals its existence and well-being. Perform the
-            // UPnP "description" phase by downloading and decoding the
-            // description document.
-
-            DiscoveredTask *tp = new DiscoveredTask(1, disco);
-
+        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;
+                }
+            } 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;
             {
-                // Note that this does not prevent multiple successive
-                // downloads of a normal url, just multiple
-                // simultaneous downloads of a slow one, to avoid
-                // tying up threads.
-                PTMutexLocker lock(o_downloading_mutex);
-                auto res = o_downloading.insert(tp->url);
-                if (!res.second) {
-                    LOGDEB("discovery:cllb: already downloading " << 
-                           tp->url << endl);
-                    return UPNP_E_SUCCESS;
+                PTMutexLocker lock(m_lock);
+                UpnpService* serv = m_servicemap[*it];
+                if (!serv->getEventData(all, names, values) || names.empty()) {
+                    continue;
                 }
             }
-
-            LOGDEB("discoExplorer: downloading " << tp->url << endl);
-            string sdesc;
-            if (!downloadUrlWithCurl(tp->url, tp->description, 5)) {
-                LOGERR("discovery:cllb: downloadUrlWithCurl error for: " << 
-                       tp->url << endl);
-                {PTMutexLocker lock(o_downloading_mutex);
-                    o_downloading.erase(tp->url);
-                }
-                delete tp;
-                return UPNP_E_SUCCESS;
-            }
-            LOGDEB1("discovery:cllb: downloaded description document of " <<
-                    tp->description.size() << " bytes" << endl);
-
-            {PTMutexLocker lock(o_downloading_mutex);
-                o_downloading.erase(tp->url);
-            }
-
-            if (discoveredQueue.put(tp)) {
-                return UPNP_E_FINISH;
-            }
-        }
-        break;
-    }
-    case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
-    {
-        struct Upnp_Discovery *disco = (struct Upnp_Discovery *)evp;
-        //LOGDEB("discovery:cllB:BYEBYE: " << cluDiscoveryToStr(disco) << endl);
-        DiscoveredTask *tp = new DiscoveredTask(0, disco);
-        if (discoveredQueue.put(tp)) {
-            return UPNP_E_FINISH;
-        }
-        break;
-    }
-    default:
-        // Ignore other events for now
-        LOGDEB("discovery:cluCallBack: unprocessed evt type: [" << 
-               LibUPnP::evTypeAsString(et) << "]"  << endl);
-        break;
-    }
-
-    return UPNP_E_SUCCESS;
-}
-
-// Our client can set up functions to be called when we process a new device.
-// This is used during startup, when the pool is not yet complete, to enable
-// finding and listing devices as soon as they appear.
-static vector<UPnPDeviceDirectory::Visitor> o_callbacks;
-static PTMutexInit o_callbacks_mutex;
-
-unsigned int UPnPDeviceDirectory::addCallback(UPnPDeviceDirectory::Visitor v)
-{
-    PTMutexLocker lock(o_callbacks_mutex);
-    o_callbacks.push_back(v);
-    return o_callbacks.size() - 1;
-}
-
-void UPnPDeviceDirectory::delCallback(unsigned int idx)
-{
-    PTMutexLocker lock(o_callbacks_mutex);
-    if (idx >= o_callbacks.size())
-        return;
-    o_callbacks.erase(o_callbacks.begin() + idx);
-}
-
-// Descriptor kept in the device pool for each device found on the network.
-class DeviceDescriptor {
-public:
-    DeviceDescriptor(const string& url, const string& description,
-                     time_t last, int exp)
-        : device(url, description), last_seen(last), expires(exp+20)
-        {}
-    DeviceDescriptor()
-        {}
-    UPnPDeviceDesc device;
-    time_t last_seen;
-    int expires; // seconds valid
-};
-
-// A DevicePool holds the characteristics of the devices
-// currently on the network.
-// The map is referenced by deviceId (==UDN)
-// The class is instanciated as a static (unenforced) singleton.
-class DevicePool {
-public:
-    PTMutexInit m_mutex;
-    map<string, DeviceDescriptor> m_devices;
-};
-static DevicePool o_pool;
-typedef map<string, DeviceDescriptor>::iterator DevPoolIt;
-
-// Worker routine for the discovery queue. Get messages about devices
-// appearing and disappearing, and update the directory pool
-// accordingly.
-void *UPnPDeviceDirectory::discoExplorer(void *)
-{
-    for (;;) {
-        DiscoveredTask *tsk = 0;
-        size_t qsz;
-        if (!discoveredQueue.take(&tsk, &qsz)) {
-            discoveredQueue.workerExit();
-            return (void*)1;
-        }
-        LOGDEB1("discoExplorer: got task: alive " << tsk->alive << " deviceId ["
-                << tsk->deviceId << " URL [" << tsk->url << "]" << endl);
-
-        if (!tsk->alive) {
-            // Device signals it is going off.
-            PTMutexLocker lock(o_pool.m_mutex);
-            DevPoolIt it = o_pool.m_devices.find(tsk->deviceId);
-            if (it != o_pool.m_devices.end()) {
-                o_pool.m_devices.erase(it);
-                //LOGDEB("discoExplorer: delete " << tsk->deviceId.c_str() << 
-                // endl);
-            }
-        } else {
-            // Update or insert the device
-            DeviceDescriptor d(tsk->url, tsk->description, time(0), tsk->expires);
-            if (!d.device.ok) {
-                LOGERR("discoExplorer: description parse failed for " << 
-                       tsk->deviceId << endl);
-                delete tsk;
-                continue;
-            }
-            LOGDEB1("discoExplorer: found id [" << tsk->deviceId  << "]" 
-                    << " name " << d.device.friendlyName 
-                    << " devtype " << d.device.deviceType << endl);
-            {
-                PTMutexLocker lock(o_pool.m_mutex);
-                //LOGDEB1("discoExplorer: inserting device id "<< tsk->deviceId
-                // <<  " description: " << endl << d.device.dump() << endl);
-                o_pool.m_devices[tsk->deviceId] = d;
-            }
-            {
-                PTMutexLocker lock(o_callbacks_mutex);
-                for (auto cbp = o_callbacks.begin(); 
-                     cbp != o_callbacks.end(); cbp++) {
-                    (*cbp)(d.device, UPnPServiceDesc());
-                }
-            }
-        }
-        delete tsk;
-    }
-}
-
-// Look at the devices and get rid of those which have not been seen
-// for too long. We do this when listing the top directory
-void UPnPDeviceDirectory::expireDevices()
-{
-    LOGDEB1("discovery: expireDevices:" << endl);
-    PTMutexLocker lock(o_pool.m_mutex);
-    time_t now = time(0);
-    bool didsomething = false;
-
-    for (DevPoolIt it = o_pool.m_devices.begin();
-         it != o_pool.m_devices.end();) {
-        LOGDEB1("Dev in pool: type: " << it->second.device.deviceType <<
-                " friendlyName " << it->second.device.friendlyName << endl);
-        if (now - it->second.last_seen > it->second.expires) {
-            //LOGDEB("expireDevices: deleting " <<  it->first.c_str() << " " << 
-            //   it->second.device.friendlyName.c_str() << endl);
-            o_pool.m_devices.erase(it++);
-            didsomething = true;
-        } else {
-            it++;
-        }
-    }
-    if (didsomething)
-        search();
-}
-
-// m_searchTimeout is the UPnP device search timeout, which should
-// actually be called delay because it's the base of a random delay
-// that the devices apply to avoid responding all at the same time.
-// This means that you have to wait for the specified period before
-// the results are complete.
-UPnPDeviceDirectory::UPnPDeviceDirectory(time_t search_window)
-    : m_ok(false), m_searchTimeout(search_window), m_lastSearch(0)
-{
-    addCallback(std::bind(&UPnPDeviceDirectory::deviceFound, this, _1, _2));
-
-    if (!discoveredQueue.start(1, discoExplorer, 0)) {
-        m_reason = "Discover work queue start failed";
-        return;
-    }
-    sched_yield();
-    LibUPnP *lib = LibUPnP::getLibUPnP();
-    if (lib == 0) {
-        m_reason = "Can't get lib";
-        return;
-    }
-    lib->registerHandler(UPNP_DISCOVERY_SEARCH_RESULT, cluCallBack, this);
-    lib->registerHandler(UPNP_DISCOVERY_ADVERTISEMENT_ALIVE,
-                         cluCallBack, this);
-    lib->registerHandler(UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE,
-                         cluCallBack, this);
-
-    m_ok = search();
-}
-
-bool UPnPDeviceDirectory::search()
-{
-    LOGDEB1("UPnPDeviceDirectory::search" << endl);
-    if (time(0) - m_lastSearch < 10)
-        return true;
-
-    LibUPnP *lib = LibUPnP::getLibUPnP();
-    if (lib == 0) {
-        m_reason = "Can't get lib";
-        return false;
-    }
-
-    LOGDEB1("UPnPDeviceDirectory::search: calling upnpsearchasync"<<endl);
-    //const char *cp = "ssdp:all";
-    const char *cp = "upnp:rootdevice";
-    int code1 = UpnpSearchAsync(lib->getclh(), m_searchTimeout, cp, lib);
-    if (code1 != UPNP_E_SUCCESS) {
-        m_reason = LibUPnP::errAsString("UpnpSearchAsync", code1);
-        LOGERR("UPnPDeviceDirectory::search: UpnpSearchAsync failed: " <<
-               m_reason << endl);
-    }
-    m_lastSearch = time(0);
-    return true;
-}
-
-UPnPDeviceDirectory *UPnPDeviceDirectory::getTheDir(time_t search_window)
-{
-    if (theDevDir == 0)
-        theDevDir = new UPnPDeviceDirectory(search_window);
-    if (theDevDir && !theDevDir->ok())
-        return 0;
-    return theDevDir;
-}
-
-void UPnPDeviceDirectory::terminate()
-{
-    discoveredQueue.setTerminateAndWait();
-}
-
-time_t UPnPDeviceDirectory::getRemainingDelay()
-{
-    time_t now = time(0);
-    if (now - m_lastSearch >= m_searchTimeout)
-        return 0;
-    return  m_searchTimeout - (now - m_lastSearch);
-}
-
-bool UPnPDeviceDirectory::traverse(UPnPDeviceDirectory::Visitor visit)
-{
-    //LOGDEB("UPnPDeviceDirectory::traverse" << endl);
-    if (m_ok == false)
-        return false;
-    int secs = getRemainingDelay();
-    if (secs > 0)
-        sleep(secs);
-
-    // Has locking, do it before our own lock
-    expireDevices();
-
-    PTMutexLocker lock(o_pool.m_mutex);
-
-    for (auto it = o_pool.m_devices.begin(); 
-         it != o_pool.m_devices.end(); it++) {
-        for (auto it1 = it->second.device.services.begin();
-             it1 != it->second.device.services.end(); it1++) {
-            if (!visit(it->second.device, *it1))
-                return false;
-        }
-    }
-    return true;
-}
-
-static PTMutexInit devWaitLock;
-static pthread_cond_t devWaitCond = PTHREAD_COND_INITIALIZER;
-
-bool UPnPDeviceDirectory::deviceFound(const UPnPDeviceDesc&, 
-                                      const UPnPServiceDesc&)
-{
-    PTMutexLocker lock(devWaitLock);
-    pthread_cond_broadcast(&devWaitCond);
-    return true;
-}
-
-bool UPnPDeviceDirectory::getDevBySelector(bool cmp(const UPnPDeviceDesc& ddesc,
-                                                    const string&), 
-                                           const string& value,
-                                           UPnPDeviceDesc& ddesc)
-{
-    // Has locking, do it before our own lock
-    expireDevices();
-
-    struct timespec wkuptime;
-    long long nanos = getRemainingDelay() * 1000*1000*1000;
-    clock_gettime(CLOCK_REALTIME, &wkuptime);
-    UPnPP::timespec_addnanos(&wkuptime, nanos);
-    do {
-        PTMutexLocker lock(devWaitLock);
-        {
-            PTMutexLocker lock(o_pool.m_mutex);
-            for (auto it = o_pool.m_devices.begin(); 
-                 it != o_pool.m_devices.end(); it++) {
-                if (!cmp(it->second.device, value)) {
-                    ddesc = it->second.device;
-                    return true;
-                }
-            }
-        }
-
-        if (nanos > 0) {
-            pthread_cond_timedwait(&devWaitCond, lock.getMutex(), &wkuptime);
-        }
-    } while (getRemainingDelay() > 0);
-    return false;
-}
-
-static bool cmpFName(const UPnPDeviceDesc& ddesc, const string& fname)
-{
-    return ddesc.friendlyName.compare(fname);
-}
-
-bool UPnPDeviceDirectory::getDevByFName(const string& fname, 
-                                        UPnPDeviceDesc& ddesc)
-{
-    return getDevBySelector(cmpFName, fname, ddesc);
-}
-
-static bool cmpUDN(const UPnPDeviceDesc& ddesc, const string& value)
-{
-    return ddesc.UDN.compare(value);
-}
-
-bool UPnPDeviceDirectory::getDevByUDN(const string& value, 
-                                      UPnPDeviceDesc& ddesc)
-{
-    return getDevBySelector(cmpUDN, value, ddesc);
-}
-
-
-
-} // namespace UPnPClient
+            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
src/control/mediaserver.cxx to libupnpp/control/ohplaylist.hxx
--- a/src/control/mediaserver.cxx
+++ b/libupnpp/control/ohplaylist.hxx
@@ -14,80 +14,88 @@
  *       Free Software Foundation, Inc.,
  *       59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
+#ifndef _OHPLAYLIST_HXX_INCLUDED_
+#define _OHPLAYLIST_HXX_INCLUDED_
 
-#include "libupnpp/control/mediaserver.hxx"
-
-#include <functional>                   // for _Bind, bind, _1, _2
-#include <ostream>                      // for endl
+#include <memory>                       // for shared_ptr
 #include <string>                       // for string
-#include <unordered_map>                // for unordered_map, etc
-#include <utility>                      // for pair
+#include <unordered_map>                // for unordered_map
 #include <vector>                       // for vector
 
-#include "libupnpp/control/cdirectory.hxx"  // for ContentDirectory, CDSH
-#include "libupnpp/control/description.hxx"  // for UPnPDeviceDesc, etc
-#include "libupnpp/control/discovery.hxx"  // for UPnPDeviceDirectory, etc
-#include "libupnpp/log.hxx"             // for LOGERR
+#include "cdircontent.hxx"              // for UPnPDirObject
+#include "service.hxx"                  // for Service
 
-using namespace std;
-using namespace std::placeholders;
+namespace UPnPClient { class OHPlaylist; }
+namespace UPnPClient { class UPnPDeviceDesc; }
+namespace UPnPClient { class UPnPServiceDesc; }
 
 namespace UPnPClient {
 
-const string 
-MediaServer::DType("urn:schemas-upnp-org:device:MediaServer:1");
+typedef std::shared_ptr<OHPlaylist> OHPLH;
 
-// We don't include a version in comparisons, as we are satisfied with
-// version 1
-bool MediaServer::isMSDevice(const string& st)
-{
-    const string::size_type sz(DType.size()-2);
-    return !DType.compare(0, sz, st, 0, sz);
-}
+/**
+ * OHPlaylist Service client class.
+ *
+ */
+class OHPlaylist : public Service {
+public:
 
-static bool MDAccum(unordered_map<string, UPnPDeviceDesc>* out,
-                    const string& friendlyName,
-                    const UPnPDeviceDesc& device, 
-                    const UPnPServiceDesc& service)
-{
-    //LOGDEB("MDAccum: friendlyname: " << friendlyName << 
-    //    " dev friendlyName " << device.friendlyName << endl);
-    if (ContentDirectory::isCDService(service.serviceType) &&
-        (friendlyName.empty() ? true : 
-         !friendlyName.compare(device.friendlyName))) {
-        //LOGDEB("MDAccum setting " << device.UDN << endl);
-        (*out)[device.UDN] = device;
+    OHPlaylist(const UPnPDeviceDesc& device, const UPnPServiceDesc& service)
+        : Service(device, service, true) {
+        registerCallback();
     }
-    return true;
-}
 
-bool MediaServer::getDeviceDescs(vector<UPnPDeviceDesc>& devices, 
-                                   const string& friendlyName)
-{
-    unordered_map<string, UPnPDeviceDesc> mydevs;
+    OHPlaylist() {}
 
-    UPnPDeviceDirectory::Visitor visitor = bind(MDAccum, &mydevs, friendlyName,
-                                                _1, _2);
-    UPnPDeviceDirectory::getTheDir()->traverse(visitor);
-    for (auto it = mydevs.begin(); it != mydevs.end(); it++)
-        devices.push_back(it->second);
-    return !devices.empty();
-}
+    /** Test service type from discovery message */
+    static bool isOHPlService(const std::string& st);
 
-MediaServer::MediaServer(const UPnPDeviceDesc& desc)
-{
-    bool found = false;
-    for (auto it = desc.services.begin(); it != desc.services.end(); it++) {
-        if (ContentDirectory::isCDService(it->serviceType)) {
-            m_cds = CDSH(new ContentDirectory(desc, *it));
-            found = true;
-            break;
-        }
-    }
-    if (!found) {
-        LOGERR("MediaServer::MediaServer: ContentDirectory service not "
-               "found in device" << endl);
-    }
-}
+    int play();
+    int pause();
+    int stop();
+    int next();
+    int previous();
+    int setRepeat(bool onoff);
+    int repeat(bool *on);
+    int setShuffle(bool onoff);
+    int shuffle(bool *on);
+    int seekSecondAbsolute(int value);
+    int seekSecondRelative(int value);
+    int seekId(int value);
+    int seekIndex(int value);
+    enum TPState {TPS_Unknown, TPS_Buffering, TPS_Paused, TPS_Playing,
+                  TPS_Stopped};
+    int transportState(TPState *tps);
+    int id(int *value);
+    int read(int id, std::string* uri, UPnPDirObject *dirent);
 
-}
+    struct TrackListEntry {
+        int id;
+        std::string url;
+        UPnPDirObject dirent;
+        void clear() {id = -1; url.clear(); dirent.clear();}
+    };
+    int readList(const std::vector<int>& ids, 
+                 std::vector<TrackListEntry>* entsp);
+
+    int insert(int afterid, const std::string& uri, const std::string& didl, 
+               int *nid);
+    int deleteId(int id);
+    int deleteAll();
+    int tracksMax(int *);
+    int idArray(std::vector<int> *ids, int *tokp);
+    int idArrayChanged(int token, bool *changed);
+    int protocolInfo(std::string *proto);
+
+protected:
+    /* My service type string */
+    static const std::string SType;
+
+private:
+    void evtCallback(const std::unordered_map<std::string, std::string>&);
+    void registerCallback();
+};
+
+} // namespace UPnPClient
+
+#endif /* _OHPLAYLIST_HXX_INCLUDED_ */
src/control/mediaserver.hxx to libupnpp/ixmlwrap.hxx
--- a/src/control/mediaserver.hxx
+++ b/libupnpp/ixmlwrap.hxx
@@ -1,52 +1,40 @@
-/* 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 _MEDIASERVER_HXX_INCLUDED_
-#define _MEDIASERVER_HXX_INCLUDED_
+#ifndef _IXMLWRAP_H_INCLUDED_
+#define _IXMLWRAP_H_INCLUDED_
 
-#include <memory>                       // for shared_ptr
+#include <upnp/ixml.h>                  // for IXML_Document
+
 #include <string>                       // for string
-#include <vector>                       // for vector
 
-#include "libupnpp/control/cdirectory.hxx"  // for CDSH
-#include "libupnpp/control/device.hxx"  // for Device
+namespace UPnPP {
 
-namespace UPnPClient {
+#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
 
-class MediaServer;
-class UPnPDeviceDesc;
-
-typedef std::shared_ptr<MediaServer> MSRH;
-
-class MediaServer : public Device {
-public:
-    MediaServer(const UPnPDeviceDesc& desc);
-
-    CDSH cds() {return m_cds;}
-
-    static bool getDeviceDescs(std::vector<UPnPDeviceDesc>& devices,
-                               const std::string& friendlyName = "");
-    static bool isMSDevice(const std::string& devicetype);
-
-protected:
-    CDSH m_cds;
-
-    static const std::string DType;
-};
+    /** 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*);
 
 }
 
-#endif /* _MEDIASERVER_HXX_INCLUDED_ */
+#endif /* _IXMLWRAP_H_INCLUDED_ */
src/control/ohplaylist.cxx to libupnpp/upnpplib.cxx
--- a/src/control/ohplaylist.cxx
+++ b/libupnpp/upnpplib.cxx
@@ -1,413 +1,478 @@
-/* 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.
  */
-#include "libupnpp/control/ohplaylist.hxx"
-
-#include <expat_external.h>             // for XML_Char
-#include <netinet/in.h>                 // for ntohl
-#include <stdlib.h>                     // for atoi
-#include <string.h>                     // for strcmp
-#include <upnp/upnp.h>                  // for UPNP_E_BAD_RESPONSE, etc
-
-#include <functional>                   // for _Bind, bind, _1
-#include <ostream>                      // for endl, basic_ostream, etc
+#include "config.h"
+
+#include "upnpplib.hxx"
+
+#include <ctype.h>                      // for toupper
+#include <stdio.h>                      // for sprintf
+#include <string.h>                     // for strncpy
+#include <time.h>                       // for timespec
+
+#include <upnp/ixml.h>                  // for ixmlRelaxParser
+#include <upnp/upnptools.h>             // for UpnpGetErrorMessage
+#include <upnp/upnpdebug.h>
+
+#include <iostream>                     // for operator<<, basic_ostream, etc
+#include <map>                          // for map, _Rb_tree_iterator, etc
+#include <set>                          // for set
+#include <sstream>                      // for ostringstream
 #include <string>                       // for string, basic_string, etc
 #include <utility>                      // for pair
 #include <vector>                       // for vector
 
-#include "libupnpp/base64.hxx"          // for base64_decode
-#include "libupnpp/control/cdircontent.hxx"  // for UPnPDirContent, etc
-#include "libupnpp/control/service.hxx"  // for VarEventReporter, Service
-#include "libupnpp/expatmm.hxx"         // for inputRefXMLParser
-#include "libupnpp/log.hxx"             // for LOGERR, LOGDEB1, LOGINF
-#include "libupnpp/soaphelp.hxx"        // for SoapDecodeOutput, etc
-#include "libupnpp/upnpp_p.hxx"         // for stringToBool
+#include "getsyshwaddr.h"               // for getsyshwaddr
+
+#include "libupnpp/ptmutex.hxx"         // for PTMutexLocker
+
+#include "log.hxx"                      // for LOGERR, LOGDEB1, LOGDEB, etc
+#include "md5.hxx"                      // for MD5String
 
 using namespace std;
-using namespace std::placeholders;
-using namespace UPnPP;
-
-namespace UPnPClient {
-
-const string OHPlaylist::SType("urn:av-openhome-org:service:Playlist:1");
-
-// Check serviceType string (while walking the descriptions. We don't
-// include a version in comparisons, as we are satisfied with version1
-bool OHPlaylist::isOHPlService(const string& st)
-{
-    const string::size_type sz(SType.size()-2);
-    return !SType.compare(0, sz, st, 0, sz);
-}
-
-static int stringToTpState(const string& value, OHPlaylist::TPState *tpp)
-{
-    if (!value.compare("Buffering")) {
-        *tpp = OHPlaylist::TPS_Buffering;
+
+namespace UPnPP {
+
+static LibUPnP *theLib;
+
+LibUPnP *LibUPnP::getLibUPnP(bool serveronly, string* hwaddr,
+                             const string ifname, const string ip,
+                             unsigned short port)
+{
+    if (theLib == 0)
+        theLib = new LibUPnP(serveronly, hwaddr, ifname, ip, port);
+    if (theLib && !theLib->ok()) {
+        delete theLib;
+        theLib = 0;
         return 0;
-    } else if (!value.compare("Paused")) {
-        *tpp = OHPlaylist::TPS_Paused;
-        return 0;
-    } else if (!value.compare("Playing")) {
-        *tpp = OHPlaylist::TPS_Playing;
-        return 0;
-    } else if (!value.compare("Stopped")) {
-        *tpp = OHPlaylist::TPS_Stopped;
-        return 0;
-    }
-    *tpp = OHPlaylist::TPS_Unknown;
-    return UPNP_E_BAD_RESPONSE;
-}
-
-// Translate IdArray: base64-encoded array of binary msb 32bits integers
-static void idArrayToVec(const string& _data, vector<int> *ids)
-{    
-    string data = base64_decode(_data);
-    const char *cp = data.c_str();
-    while (cp - data.c_str() <= int(data.size()) - 4) {
-        unsigned int *ip = (unsigned int *)cp;
-        ids->push_back(ntohl(*ip));
-        cp += 4;
-    }
-}
-
-void OHPlaylist::evtCallback(
-    const std::unordered_map<std::string, std::string>& props)
-{
-    LOGDEB1("OHPlaylist::evtCallback: m_reporter: " << m_reporter << endl);
-    for (auto it = props.begin(); it != props.end(); it++) {
-        if (!m_reporter) {
-            LOGDEB1("OHPlaylist::evtCallback: " << it->first << " -> " 
-                    << it->second << endl);
-            continue;
+    }
+    return theLib;
+}
+
+
+LibUPnP::LibUPnP(bool serveronly, string* hwaddr,
+                 const string ifname, const string inip, unsigned short port)
+    : m_ok(false)
+{
+    LOGDEB1("LibUPnP: serveronly " << serveronly << " &hwaddr " << hwaddr <<
+           " ifname [" << ifname << "] inip [" << inip << "] port " << port 
+           << endl);
+
+    // If our caller wants to retrieve an ethernet address (typically
+    // for uuid purposes), or has specified an interface we have to
+    // look at the network config.
+    const int ipalen(100);
+    char ip_address[ipalen];
+    ip_address[0] = 0;
+    if (hwaddr || !ifname.empty()) {
+        char mac[20];
+        if (getsyshwaddr(ifname.c_str(), ip_address, ipalen, mac, 13) < 0) {
+            LOGERR("LibUPnP::LibUPnP: failed retrieving addr" << endl);
+            return;
         }
-
-        if (!it->first.compare("TransportState")) {
-            TPState tp;
-            stringToTpState(it->second, &tp);
-            m_reporter->changed(it->first.c_str(), int(tp));
-
-        } else if (!it->first.compare("ProtocolInfo")) {
-            m_reporter->changed(it->first.c_str(), 
-                                it->second.c_str());
-
-        } else if (!it->first.compare("Repeat") ||
-                   !it->first.compare("Shuffle")) {
-            bool val = false;
-            stringToBool(it->second, &val);
-            m_reporter->changed(it->first.c_str(), val ? 1 : 0);
-
-        } else if (!it->first.compare("Id") ||
-                   !it->first.compare("TracksMax")) {
-            m_reporter->changed(it->first.c_str(),
-                                atoi(it->second.c_str()));
-            
-        } else if (!it->first.compare("IdArray")) {
-            // Decode IdArray. See how we call the client
-            vector<int> v;
-            idArrayToVec(it->second, &v);
-            m_reporter->changed(it->first.c_str(), v);
-
+        if (hwaddr)
+            *hwaddr = string(mac);
+    }
+
+    // If the interface name was not specified, we possibly use the
+    // supplied IP address.
+    if (ifname.empty())
+        strncpy(ip_address, inip.c_str(), ipalen);
+
+    m_init_error = UpnpInit(ip_address[0] ? ip_address : 0, port);
+
+    if (m_init_error != UPNP_E_SUCCESS) {
+        LOGERR(errAsString("UpnpInit", m_init_error) << endl);
+        return;
+    }
+    setMaxContentLength(2000*1024);
+
+    LOGDEB("LibUPnP: Using IP " << UpnpGetServerIpAddress() << " port " << 
+           UpnpGetServerPort() << endl);
+
+#if defined(HAVE_UPNPSETLOGLEVEL)
+    UpnpCloseLog();
+#endif
+
+    // Client initialization is simple, just do it. Defer device
+    // initialization because it's more complicated.
+    if (serveronly) {
+        m_ok = true;
+    } else {
+        m_init_error = UpnpRegisterClient(o_callback, (void *)this, &m_clh);
+		
+        if (m_init_error == UPNP_E_SUCCESS) {
+            m_ok = true;
         } else {
-            LOGERR("OHPlaylist event: unknown variable: name [" <<
-                   it->first << "] value [" << it->second << endl);
-                m_reporter->changed(it->first.c_str(), it->second.c_str());
+            LOGERR(errAsString("UpnpRegisterClient", m_init_error) << endl);
         }
     }
-}
-
-void OHPlaylist::registerCallback()
-{
-    Service::registerCallback(bind(&OHPlaylist::evtCallback, this, _1));
-}
-
-int OHPlaylist::play()
-{
-    return runTrivialAction("Play");
-}
-int OHPlaylist::pause()
-{
-    return runTrivialAction("Pause");
-}
-int OHPlaylist::stop()
-{
-    return runTrivialAction("Stop");
-}
-int OHPlaylist::next()
-{
-    return runTrivialAction("Next");
-}
-int OHPlaylist::previous()
-{
-    return runTrivialAction("Previous");
-}
-int OHPlaylist::setRepeat(bool onoff)
-{
-    return runSimpleAction("SetRepeat", "Value", onoff);
-}
-int OHPlaylist::repeat(bool *on)
-{
-    return runSimpleGet("Repeat", "Value", on);
-}
-int OHPlaylist::setShuffle(bool onoff)
-{
-    return runSimpleAction("SetShuffle", "Value", onoff);
-}
-int OHPlaylist::shuffle(bool *on)
-{
-    return runSimpleGet("Shuffle", "Value", on);
-}
-int OHPlaylist::seekSecondAbsolute(int value)
-{
-    return runSimpleAction("SeekSecondAbsolute", "Value", value);
-}
-int OHPlaylist::seekSecondRelative(int value)
-{
-    return runSimpleAction("SeekSecondRelative", "Value", value);
-}
-int OHPlaylist::seekId(int value)
-{
-    return runSimpleAction("SeekId", "Value", value);
-}
-int OHPlaylist::seekIndex(int value)
-{
-    return runSimpleAction("SeekIndex", "Value", value);
-}
-
-int OHPlaylist::transportState(TPState* tpp)
-{
-    string value;
-    int ret;
-
-    if ((ret = runSimpleGet("TransportState", "Value", &value)))
-        return ret;
-
-    return stringToTpState(value, tpp);
-}
-
-int OHPlaylist::id(int *value)
-{
-    return runSimpleGet("Id", "Value", value);
-}
-
-int OHPlaylist::read(int id, std::string* urip, UPnPDirObject *dirent)
-{
-    SoapEncodeInput args(m_serviceType, "Read");
-    args("Id", SoapHelp::i2s(id));
-    SoapDecodeOutput data;
-    int ret = runAction(args, data);
-    if (ret != UPNP_E_SUCCESS) {
-        return ret;
-    }
-    if (!data.get("Uri", urip)) {
-        LOGERR("OHPlaylist::Read: missing Uri in response" << endl);
-        return UPNP_E_BAD_RESPONSE;
-    }
-    string didl;
-    if (!data.get("Metadata", &didl)) {
-        LOGERR("OHPlaylist::Read: missing Uri in response" << endl);
-        return UPNP_E_BAD_RESPONSE;
-    }
-    didl = SoapHelp::xmlUnquote(didl);
-
-    UPnPDirContent dir;
-    if (!dir.parse(didl)) {
-        LOGERR("OHPlaylist::Read: didl parse failed: " << didl << endl);
-        return UPNP_E_BAD_RESPONSE;
-    }
-    if (dir.m_items.size() != 1) {
-        LOGERR("OHPlaylist::Read: " << dir.m_items.size() << " in response!" <<
-               endl);
-        return UPNP_E_BAD_RESPONSE;
-    }
-    *dirent = dir.m_items[0];
-    return 0;
-}
-
-// Tracklist format
-// <TrackList>
-//   <Entry>
-//     <Id>10</Id>
-//     <Uri>http://blabla</Uri>
-//     <Metadata>(xmlencoded didl)</Metadata>
-//   </Entry>
-// </TrackList>
-
-
-class OHTrackListParser : public inputRefXMLParser {
-public:
-    OHTrackListParser(const string& input, 
-                      vector<OHPlaylist::TrackListEntry>* vp)
-        : inputRefXMLParser(input), m_v(vp)
-        {
-            //LOGDEB("OHTrackListParser: input: " << input << endl);
+
+    // Servers sometimes make errors (e.g.: minidlna returns bad utf-8).
+    ixmlRelaxParser(1);
+}
+
+int LibUPnP::setupWebServer(const string& description, UpnpDevice_Handle *dvh)
+{
+    int res = UpnpRegisterRootDevice2(
+        UPNPREG_BUF_DESC,
+        description.c_str(), 
+        description.size(), /* Desc filename len, ignored */
+        1, /* config_baseURL */
+        o_callback, (void *)this, dvh);
+
+    if (res != UPNP_E_SUCCESS) {
+        LOGERR(errAsString("UpnpRegisterRootDevice2", res) << " description " <<
+               description << endl);
+    }
+    return res;
+}
+
+void LibUPnP::setMaxContentLength(int bytes)
+{
+    UpnpSetMaxContentLength(bytes);
+}
+
+bool LibUPnP::setLogFileName(const std::string& fn, LogLevel level)
+{
+    PTMutexLocker lock(m_mutex);
+    if (fn.empty() || level == LogLevelNone) {
+#if defined(HAVE_UPNPSETLOGLEVEL)
+        UpnpCloseLog();
+#endif
+    } else {
+#if defined(HAVE_UPNPSETLOGLEVEL)
+        setLogLevel(level);
+        UpnpSetLogFileNames(fn.c_str(), fn.c_str());
+        int code = UpnpInitLog();
+        if (code != UPNP_E_SUCCESS) {
+            LOGERR(errAsString("UpnpInitLog", code) << endl);
+            return false;
         }
-
-protected:
-    virtual void StartElement(const XML_Char *name, const XML_Char **) {
-        m_path.push_back(name);
-    }
-    virtual void EndElement(const XML_Char *name) {
-        if (!strcmp(name, "Entry")) {
-            UPnPDirContent dir;
-            if (!dir.parse(m_tdidl)) {
-                LOGERR("OHPlaylist::ReadList: didl parse failed: " 
-                       << m_tdidl << endl);
-                return;
+#endif
+    }
+    return true;
+}
+
+bool LibUPnP::setLogLevel(LogLevel level)
+{
+#if defined(HAVE_UPNPSETLOGLEVEL)
+    switch (level) {
+    case LogLevelNone: 
+        setLogFileName("", LogLevelNone);
+        break;
+    case LogLevelError: 
+        UpnpSetLogLevel(UPNP_CRITICAL);
+        break;
+    case LogLevelDebug: 
+        UpnpSetLogLevel(UPNP_ALL);
+        break;
+    }
+#endif
+    return true;
+}
+
+void LibUPnP::registerHandler(Upnp_EventType et, Upnp_FunPtr handler,
+                              void *cookie)
+{
+    PTMutexLocker lock(m_mutex);
+    if (handler == 0) {
+        m_handlers.erase(et);
+    } else {
+        Handler h(handler, cookie);
+        m_handlers[et] = h;
+    }
+}
+
+std::string LibUPnP::errAsString(const std::string& who, int code)
+{
+    std::ostringstream os;
+    os << who << " :" << code << ": " << UpnpGetErrorMessage(code);
+    return os.str();
+}
+
+int LibUPnP::o_callback(Upnp_EventType et, void* evp, void* cookie)
+{
+    LibUPnP *ulib = (LibUPnP *)cookie;
+    if (ulib == 0) {
+        // Because the asyncsearch calls uses a null cookie.
+        //cerr << "o_callback: NULL ulib!" << endl;
+        ulib = theLib;
+    }
+    LOGDEB1("LibUPnP::o_callback: event type: " << evTypeAsString(et) << endl);
+
+    map<Upnp_EventType, Handler>::iterator it = ulib->m_handlers.find(et);
+    if (it != ulib->m_handlers.end()) {
+        (it->second.handler)(et, evp, it->second.cookie);
+    }
+    return UPNP_E_SUCCESS;
+}
+
+LibUPnP::~LibUPnP()
+{
+    int error = UpnpFinish();
+    if (error != UPNP_E_SUCCESS) {
+        LOGINF("LibUPnP::~LibUPnP: " << errAsString("UpnpFinish", error)
+               << endl);
+    }
+    LOGDEB1("LibUPnP: done" << endl);
+}
+
+string LibUPnP::makeDevUUID(const std::string& name, const std::string& hw)
+{
+    string digest;
+    MD5String(name, digest);
+    // digest has 16 bytes of binary data. UUID is like:
+    // f81d4fae-7dec-11d0-a765-00a0c91e6bf6
+    // Where the last 12 chars are provided by the hw addr
+
+    char uuid[100];
+    sprintf(uuid, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%s",
+            digest[0]&0xff, digest[1]&0xff, digest[2]&0xff, digest[3]&0xff,
+            digest[4]&0xff, digest[5]&0xff,  digest[6]&0xff, digest[7]&0xff,
+            digest[8]&0xff, digest[9]&0xff, hw.c_str());
+    return uuid;
+}
+
+
+string LibUPnP::evTypeAsString(Upnp_EventType et)
+{
+    switch (et) {
+    case UPNP_CONTROL_ACTION_REQUEST: return "UPNP_CONTROL_ACTION_REQUEST";
+    case UPNP_CONTROL_ACTION_COMPLETE: return "UPNP_CONTROL_ACTION_COMPLETE";
+    case UPNP_CONTROL_GET_VAR_REQUEST: return "UPNP_CONTROL_GET_VAR_REQUEST";
+    case UPNP_CONTROL_GET_VAR_COMPLETE: return "UPNP_CONTROL_GET_VAR_COMPLETE";
+    case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
+        return "UPNP_DISCOVERY_ADVERTISEMENT_ALIVE";
+    case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
+        return "UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE";
+    case UPNP_DISCOVERY_SEARCH_RESULT: return "UPNP_DISCOVERY_SEARCH_RESULT";
+    case UPNP_DISCOVERY_SEARCH_TIMEOUT: return "UPNP_DISCOVERY_SEARCH_TIMEOUT";
+    case UPNP_EVENT_SUBSCRIPTION_REQUEST:
+        return "UPNP_EVENT_SUBSCRIPTION_REQUEST";
+    case UPNP_EVENT_RECEIVED: return "UPNP_EVENT_RECEIVED";
+    case UPNP_EVENT_RENEWAL_COMPLETE: return "UPNP_EVENT_RENEWAL_COMPLETE";
+    case UPNP_EVENT_SUBSCRIBE_COMPLETE: return "UPNP_EVENT_SUBSCRIBE_COMPLETE";
+    case UPNP_EVENT_UNSUBSCRIBE_COMPLETE:
+        return "UPNP_EVENT_UNSUBSCRIBE_COMPLETE";
+    case UPNP_EVENT_AUTORENEWAL_FAILED: return "UPNP_EVENT_AUTORENEWAL_FAILED";
+    case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
+        return "UPNP_EVENT_SUBSCRIPTION_EXPIRED";
+    default: return "UPNP UNKNOWN EVENT";
+    }
+}
+
+/////////////////////// Small global helpers
+
+/** Get rid of white space at both ends */
+void trimstring(string &s, const char *ws)
+{
+    string::size_type pos = s.find_first_not_of(ws);
+    if (pos == string::npos) {
+        s.clear();
+        return;
+    }
+    s.replace(0, pos, string());
+
+    pos = s.find_last_not_of(ws);
+    if (pos != string::npos && pos != s.length()-1)
+        s.replace(pos+1, string::npos, string());
+}
+
+string caturl(const string& s1, const string& s2)
+{
+    string out(s1);
+    if (out[out.size()-1] == '/') {
+        if (s2[0] == '/')
+            out.erase(out.size()-1);
+    } else {
+        if (s2[0] != '/')
+            out.push_back('/');
+    }
+    out += s2;
+    return out;
+}
+
+string baseurl(const string& url)
+{
+    string::size_type pos = url.find("://");
+    if (pos == string::npos)
+        return url;
+
+    pos = url.find_first_of("/", pos + 3);
+    if (pos == string::npos) {
+        return url;
+    } else {
+        return url.substr(0, pos + 1);
+    }
+}
+
+static void path_catslash(string &s) {
+    if (s.empty() || s[s.length() - 1] != '/')
+        s += '/';
+}
+string path_getfather(const string &s)
+{
+    string father = s;
+
+    // ??
+    if (father.empty())
+        return "./";
+
+    if (father[father.length() - 1] == '/') {
+        // Input ends with /. Strip it, handle special case for root
+        if (father.length() == 1)
+            return father;
+        father.erase(father.length()-1);
+    }
+
+    string::size_type slp = father.rfind('/');
+    if (slp == string::npos)
+        return "./";
+
+    father.erase(slp);
+    path_catslash(father);
+    return father;
+}
+string path_getsimple(const string &s) {
+    string simple = s;
+
+    if (simple.empty())
+        return simple;
+
+    string::size_type slp = simple.rfind('/');
+    if (slp == string::npos)
+        return simple;
+
+    simple.erase(0, slp+1);
+    return simple;
+}
+
+template <class T> bool csvToStrings(const string &s, T &tokens)
+{
+    string current;
+    tokens.clear();
+    enum states {TOKEN, ESCAPE};
+    states state = TOKEN;
+    for (unsigned int i = 0; i < s.length(); i++) {
+        switch (s[i]) {
+        case ',':
+            switch(state) {
+            case TOKEN:
+                tokens.insert(tokens.end(), current);
+                current.clear();
+                continue;
+            case ESCAPE:
+                current += ',';
+                state = TOKEN;
+                continue;
             }
-            if (dir.m_items.size() != 1) {
-                LOGERR("OHPlaylist::ReadList: " << dir.m_items.size() 
-                       << " in response!" << endl);
-                return;
+            break;
+        case '\\':
+            switch(state) {
+            case TOKEN:
+                state=ESCAPE;
+                continue;
+            case ESCAPE:
+                current += '\\';
+                state = TOKEN;
+                continue;
             }
-            m_tt.dirent = dir.m_items[0];
-            m_v->push_back(m_tt);
-            m_tt.clear();
-            m_tdidl.clear();
+            break;
+
+        default:
+            switch(state) {
+            case ESCAPE:
+                state = TOKEN;
+                break;
+            case TOKEN:
+                break;
+            }
+            current += s[i];
         }
-        m_path.pop_back();
-    }
-    virtual void CharacterData(const XML_Char *s, int len) {
-        if (s == 0 || *s == 0)
-            return;
-        string str(s, len);
-        if (!m_path.back().compare("Id"))
-            m_tt.id = atoi(str.c_str());
-        else if (!m_path.back().compare("Uri"))
-            m_tt.url = str;
-        else if (!m_path.back().compare("Metadata"))
-            m_tdidl += str;
-    }
-
-private:
-    vector<OHPlaylist::TrackListEntry>* m_v;
-    std::vector<std::string> m_path;
-    OHPlaylist::TrackListEntry m_tt;
-    string m_tdidl;
-};
-
-int OHPlaylist::readList(const std::vector<int>& ids, 
-                         vector<TrackListEntry>* entsp)
-{
-    string idsparam;
-    for (auto it = ids.begin(); it != ids.end(); it++) {
-        idsparam += SoapHelp::i2s(*it) + " ";
-    }
-    entsp->clear();
-
-    SoapEncodeInput args(m_serviceType, "ReadList");
-    args("IdList", idsparam);
-    SoapDecodeOutput data;
-    int ret = runAction(args, data);
-    if (ret != UPNP_E_SUCCESS) {
-        return ret;
-    }
-    string xml;
-    if (!data.get("TrackList", &xml)) {
-        LOGERR("OHPlaylist::readlist: missing TrackList in response" << endl);
-        return UPNP_E_BAD_RESPONSE;
-    }
-    OHTrackListParser mparser(xml, entsp);
-    if (!mparser.Parse())
-        return UPNP_E_BAD_RESPONSE;
-    return 0;
-}
-
-int OHPlaylist::insert(int afterid, const string& uri, const string& didl, 
-                       int *nid)
-{
-    SoapEncodeInput args(m_serviceType, "Insert");
-    args("AfterId", SoapHelp::i2s(afterid))
-        ("Uri", uri)
-        ("Metadata", didl);
-    SoapDecodeOutput data;
-    int ret = runAction(args, data);
-    if (ret != UPNP_E_SUCCESS) {
-        return ret;
-    }
-    if (!data.get("NewId", nid)) {
-        LOGERR("OHPlaylist::insert: missing Newid in response" << endl);
-        return UPNP_E_BAD_RESPONSE;
-    }
-    return 0;
-}
-
-
-int OHPlaylist::deleteId(int value)
-{
-    return runSimpleAction("DeleteId", "Value", value);
-}
-int OHPlaylist::deleteAll()
-{
-    return runTrivialAction("DeleteAll");
-}
-int OHPlaylist::tracksMax(int *valuep)
-{
-    return runSimpleGet("TracksMax", "Value", valuep);
-}
-
-int OHPlaylist::idArray(vector<int> *ids, int *tokp)
-{
-    SoapEncodeInput args(m_serviceType, "IdArray");
-    SoapDecodeOutput data;
-    int ret = runAction(args, data);
-    if (ret != UPNP_E_SUCCESS) {
-        return ret;
-    }
-    if (!data.get("Token", tokp)) {
-        LOGERR("OHPlaylist::idArray: missing Token in response" << endl);
-        return UPNP_E_BAD_RESPONSE;
-    }
-    string arraydata;
-    if (!data.get("Array", &arraydata)) {
-        LOGINF("OHPlaylist::idArray: missing Array in response" << endl);
-        // We get this for an empty array ? This would need to be investigated
-    }
-    idArrayToVec(arraydata, ids);
-    return 0;
-}
-
-int OHPlaylist::idArrayChanged(int token, bool *changed)
-{
-    SoapEncodeInput args(m_serviceType, "IdArrayChanged");
-    args("Token", SoapHelp::i2s(token));
-    SoapDecodeOutput data;
-    int ret = runAction(args, data);
-    if (ret != UPNP_E_SUCCESS) {
-        return ret;
-    }
-    if (!data.get("Value", changed)) {
-        LOGERR("OHPlaylist::idArrayChanged: missing Value in response" << endl);
-        return UPNP_E_BAD_RESPONSE;
-    }
-    return 0;
-}
-
-int OHPlaylist::protocolInfo(std::string *proto)
-{
-    SoapEncodeInput args(m_serviceType, "ProtocolInfo");
-    SoapDecodeOutput data;
-    int ret = runAction(args, data);
-    if (ret != UPNP_E_SUCCESS) {
-        return ret;
-    }
-    if (!data.get("Value", proto)) {
-        LOGERR("OHPlaylist::protocolInfo: missing Value in response" << endl);
-        return UPNP_E_BAD_RESPONSE;
-    }
-    return 0;
-}
-
-} // End namespace UPnPClient
+    }
+    switch(state) {
+    case TOKEN:
+        tokens.insert(tokens.end(), current);
+        break;
+    case ESCAPE:
+        return false;
+    }
+    return true;
+}
+
+//template bool csvToStrings<list<string> >(const string &, list<string> &);
+template bool csvToStrings<vector<string> >(const string &, vector<string> &);
+template bool csvToStrings<set<string> >(const string &, set<string> &);
+
+
+bool stringToBool(const string& s, bool *value)
+{
+    if (s[0] == 'F' ||s[0] == 'f' ||s[0] == 'N' || s[0] == 'n' ||s[0] == '0') {
+        *value = false;
+    } else if (s[0] == 'T'|| s[0] == 't' ||s[0] == 'Y' ||s[0] == 'y' || 
+               s[0] == '1') {
+        *value = true;
+    } else {
+        return false;
+    }
+    return true;
+}
+
+//  s1 is already uppercase
+int stringuppercmp(const string & s1, const string& s2) 
+{
+    string::const_iterator it1 = s1.begin();
+    string::const_iterator it2 = s2.begin();
+    int size1 = s1.length(), size2 = s2.length();
+    char c2;
+
+    if (size1 > size2) {
+	while (it1 != s1.end()) { 
+	    c2 = ::toupper(*it2);
+	    if (*it1 != c2) {
+		return *it1 > c2 ? 1 : -1;
+	    }
+	    ++it1; ++it2;
+	}
+	return size1 == size2 ? 0 : 1;
+    } else {
+	while (it2 != s2.end()) { 
+	    c2 = ::toupper(*it2);
+	    if (*it1 != c2) {
+		return *it1 > c2 ? 1 : -1;
+	    }
+	    ++it1; ++it2;
+	}
+	return size1 == size2 ? 0 : -1;
+    }
+}
+
+static const long long BILLION = 1000 * 1000 * 1000;
+
+void timespec_addnanos(struct timespec *ts, long long nanos)
+{
+    nanos = nanos + ts->tv_nsec;
+    int secs = 0;
+    if (nanos > BILLION) {
+        secs = nanos / BILLION;
+        nanos -= secs * BILLION;
+    } 
+    ts->tv_sec += secs;
+    ts->tv_nsec = nanos;
+}
+
+}
src/control/ohplaylist.hxx to libupnpp/control/renderingcontrol.hxx
--- a/src/control/ohplaylist.hxx
+++ b/libupnpp/control/renderingcontrol.hxx
@@ -14,88 +14,68 @@
  *       Free Software Foundation, Inc.,
  *       59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
-#ifndef _OHPLAYLIST_HXX_INCLUDED_
-#define _OHPLAYLIST_HXX_INCLUDED_
+#ifndef _RENDERINGCONTROL_HXX_INCLUDED_
+#define _RENDERINGCONTROL_HXX_INCLUDED_
 
 #include <memory>                       // for shared_ptr
 #include <string>                       // for string
 #include <unordered_map>                // for unordered_map
-#include <vector>                       // for vector
 
-#include "cdircontent.hxx"              // for UPnPDirObject
 #include "service.hxx"                  // for Service
 
-namespace UPnPClient { class OHPlaylist; }
+namespace UPnPClient { class RenderingControl; }
 namespace UPnPClient { class UPnPDeviceDesc; }
 namespace UPnPClient { class UPnPServiceDesc; }
 
 namespace UPnPClient {
 
-typedef std::shared_ptr<OHPlaylist> OHPLH;
+typedef std::shared_ptr<RenderingControl> RDCH;
 
 /**
- * OHPlaylist Service client class.
+ * RenderingControl Service client class.
  *
  */
-class OHPlaylist : public Service {
+class RenderingControl : public Service {
 public:
 
-    OHPlaylist(const UPnPDeviceDesc& device, const UPnPServiceDesc& service)
-        : Service(device, service, true) {
-        registerCallback();
-    }
+    /** Construct by copying data from device and service objects.
+     *
+     */
+    RenderingControl(const UPnPDeviceDesc& device,
+                     const UPnPServiceDesc& service);
 
-    OHPlaylist() {}
+    RenderingControl() {}
 
     /** Test service type from discovery message */
-    static bool isOHPlService(const std::string& st);
+    static bool isRDCService(const std::string& st);
 
-    int play();
-    int pause();
-    int stop();
-    int next();
-    int previous();
-    int setRepeat(bool onoff);
-    int repeat(bool *on);
-    int setShuffle(bool onoff);
-    int shuffle(bool *on);
-    int seekSecondAbsolute(int value);
-    int seekSecondRelative(int value);
-    int seekId(int value);
-    int seekIndex(int value);
-    enum TPState {TPS_Unknown, TPS_Buffering, TPS_Paused, TPS_Playing,
-                  TPS_Stopped};
-    int transportState(TPState *tps);
-    int id(int *value);
-    int read(int id, std::string* uri, UPnPDirObject *dirent);
-
-    struct TrackListEntry {
-        int id;
-        std::string url;
-        UPnPDirObject dirent;
-        void clear() {id = -1; url.clear(); dirent.clear();}
-    };
-    int readList(const std::vector<int>& ids, 
-                 std::vector<TrackListEntry>* entsp);
-
-    int insert(int afterid, const std::string& uri, const std::string& didl, 
-               int *nid);
-    int deleteId(int id);
-    int deleteAll();
-    int tracksMax(int *);
-    int idArray(std::vector<int> *ids, int *tokp);
-    int idArrayChanged(int token, bool *changed);
-    int protocolInfo(std::string *proto);
+    /** @ret 0 for success, upnp error else */
+    int setVolume(int volume, const std::string& channel = "Master");
+    int getVolume(const std::string& channel = "Master");
+    int setMute(bool mute, const std::string& channel = "Master");
+    bool getMute(const std::string& channel = "Master");
 
 protected:
     /* My service type string */
     static const std::string SType;
 
+    /* Volume settings params */
+    int m_volmin;
+    int m_volmax;
+    int m_volstep;
+
 private:
     void evtCallback(const std::unordered_map<std::string, std::string>&);
     void registerCallback();
+    /** Set volume parameters from service state variable table values */
+    void setVolParams(int min, int max, int step) {
+        m_volmin = min >= 0 ? min : 0;
+        m_volmax = max > 0 ? max : 100;
+        m_volstep = step > 0 ? step : 1;
+    }
+    int devVolTo0100(int);
 };
 
 } // namespace UPnPClient
 
-#endif /* _OHPLAYLIST_HXX_INCLUDED_ */
+#endif /* _RENDERINGCONTROL_HXX_INCLUDED_ */
src/control/renderingcontrol.hxx to libupnpp/ixmlwrap.cxx
--- a/src/control/renderingcontrol.hxx
+++ b/libupnpp/ixmlwrap.cxx
@@ -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,68 +14,53 @@
  *       Free Software Foundation, Inc.,
  *       59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
-#ifndef _RENDERINGCONTROL_HXX_INCLUDED_
-#define _RENDERINGCONTROL_HXX_INCLUDED_
+#include "config.h"
 
-#include <memory>                       // for shared_ptr
+#include "libupnpp/ixmlwrap.hxx"
+
+#include <upnp/ixml.h>                  // for IXML_Node, etc
+
 #include <string>                       // for string
-#include <unordered_map>                // for unordered_map
 
-#include "service.hxx"                  // for Service
+using std::string;
 
-namespace UPnPClient { class RenderingControl; }
-namespace UPnPClient { class UPnPDeviceDesc; }
-namespace UPnPClient { class UPnPServiceDesc; }
+namespace UPnPP {
 
-namespace UPnPClient {
+#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());
 
-typedef std::shared_ptr<RenderingControl> RDCH;
+    if (nodes) {
+        IXML_Node *first = ixmlNodeList_item(nodes, 0);
+        if (first) {
+            IXML_Node *dnode = ixmlNode_getFirstChild(first);
+            if (dnode) {
+                ret = ixmlNode_getNodeValue(dnode);
+            }
+        }
+    }
 
-/**
- * RenderingControl Service client class.
- *
- */
-class RenderingControl : public Service {
-public:
+    if(nodes)
+        ixmlNodeList_free(nodes);
+    return ret;
+}
+#endif
 
-    /** Construct by copying data from device and service objects.
-     *
-     */
-    RenderingControl(const UPnPDeviceDesc& device,
-                     const UPnPServiceDesc& service);
+string ixmlwPrintDoc(IXML_Document* doc)
+{
+    DOMString s = ixmlPrintDocument(doc);
+    if (s) {
+        string cpps(s);
+        ixmlFreeDOMString(s);
+        return cpps;
+    } else {
+        return string();
+    }
+}
 
-    RenderingControl() {}
-
-    /** Test service type from discovery message */
-    static bool isRDCService(const std::string& st);
-
-    /** @ret 0 for success, upnp error else */
-    int setVolume(int volume, const std::string& channel = "Master");
-    int getVolume(const std::string& channel = "Master");
-    int setMute(bool mute, const std::string& channel = "Master");
-    bool getMute(const std::string& channel = "Master");
-
-protected:
-    /* My service type string */
-    static const std::string SType;
-
-    /* Volume settings params */
-    int m_volmin;
-    int m_volmax;
-    int m_volstep;
-
-private:
-    void evtCallback(const std::unordered_map<std::string, std::string>&);
-    void registerCallback();
-    /** Set volume parameters from service state variable table values */
-    void setVolParams(int min, int max, int step) {
-        m_volmin = min >= 0 ? min : 0;
-        m_volmax = max > 0 ? max : 100;
-        m_volstep = step > 0 ? step : 1;
-    }
-    int devVolTo0100(int);
-};
-
-} // namespace UPnPClient
-
-#endif /* _RENDERINGCONTROL_HXX_INCLUDED_ */
+}
<< < 1 2 3 > >> (Page 2 of 3)