Switch to side-by-side view

--- a/libupnpp/device/device.hxx
+++ b/libupnpp/device/device.hxx
@@ -23,10 +23,10 @@
 #include <upnp/upnp.h>
 
 #include <functional>
-#include <unordered_map>
 #include <memory>
 #include <string>
 #include <vector>
+#include <map>
 
 #include "libupnpp/soaphelp.hxx"
 
@@ -42,7 +42,7 @@
 typedef std::function<int (const UPnPP::SoapIncoming&, UPnPP::SoapOutgoing&)>
 soapfun;
 
-// Definition of a virtual directory entry: data and mime type
+/** 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) {}
@@ -52,46 +52,55 @@
 
 /** 
  * Interface to link libupnp operations to a device implementation.
- * libupnp can only support a single root device per instance.
  */
 class UpnpDevice {
 public:
-    /** Construct device object. Do not start it (this is done by the
-     *   eventloop() call when everything is set up).
+    /** Construct a device object. 
+     *
+     * The device is not started. This will be done by the startloop() or 
+     * eventloop() call when everything is set up.
      *
      * @param deviceId uuid for device: "uuid:UUIDvalue"
-     * @param files for a root device, list of path/content pairs to
-     *  be added to the virtual directory. 
-     *  The description document *must* be named 'xxx/description.xml'
-     *  The file paths should include a sub-directory component.
-     *  The list must include the description document, but this will not
-     *  be directly served out. Instead a version modified by libupnp
-     *  (with URLBase possibly added/edited) will be served from '/'. 
-     *  Of course, the list and the paths in description.xml must be
-     *  consistent, e.g. the paths for the services SCDPURL documents.
-     *
-     *  The files parameter must be empty for embedded devices: all served 
-     *  files must be set with the root device, *including* embedded service
-     *  description files. files.empty() is also how we decide if we
-     *  are building a root or an embedded device. root should be built
-     *  first.
-     */
-    UpnpDevice(const std::string& deviceId,
-               const std::unordered_map<std::string, VDirContent>& files);
+     */
+    UpnpDevice(const std::string& deviceId);
+
+    /** Construct an embedded device.
+     *
+     * The device is not started. This will be done by the startloop() or 
+     * eventloop() call when everything is set up.
+     * @param rootdev if not null, the device description will be
+     *    stored in the <devices> section of the root device (this device
+     *    will be embedded). Else behave as the other constructor.
+     *    !! The root device must not be already started. !!
+     */
+    UpnpDevice(UpnpDevice *rootdev, const std::string& deviceId);
+    
     ~UpnpDevice();
 
-    bool ipv4(std::string *host, unsigned short *port) const;
-    
-    // We only ever keep one instance of a serviceId. Multiple calls will
-    // only keep the last one.
-    void addService(UpnpService *, const std::string& serviceId);
-    void forgetService(const std::string& serviceId);
-
-    /**
-     * Add mapping from service+action-name to handler function.
-     */
-    void addActionMapping(const UpnpService*,
-                          const std::string& actName, soapfun);
+    /** Retrieve the network endpoint the server is listening on */
+    static bool ipv4(std::string *host, unsigned short *port);
+
+    /** Librarian utility: must be implemented for the services to retrieve
+     *  their service definition XML files, 
+     *
+     * This is also used by the base class (with an empty name) to retrieve an 
+     * XML text fragment to be added to the <device> node in the device 
+     * description XML. E.G. things like <serialNumber>42</serialNumber>. 
+     * *Mandatory*: deviceType and friendlyName *must* be in there, 
+     * UDN *must not*
+     * 
+     *  @param name the designator set in the service constructor 
+     *         (e.g. AVTransport.xml). The base class uses an empty name to 
+     *         retrieve the description properties (see above).
+     *  @param[output] contents the output data.
+     *  @return false for error.
+     */
+    virtual bool readLibFile(const std::string& name,
+                             std::string& contents) = 0;
+
+    /** Add file to virtual directory. Returns path in @param path */
+    bool addVFile(const std::string& name, const std::string& contents,
+                  const std::string& mime, std::string& path);
 
     /**
      * Main routine. To be called by main() on the root device when done 
@@ -103,6 +112,15 @@
      */
     void eventloop();
 
+    /** 
+     * Start a thread to run the event loop and return immediately. 
+     *
+     * This is an alternative to running eventloop() from
+     * the main thread. The destructor will take care of the internal
+     * thread.
+     */
+    void startloop();
+    
     /**
      * To be called from a service action callback to wake up the
      * event loop early if something needs to be broadcast without
@@ -112,6 +130,8 @@
      */
     void loopWakeup(); // To trigger an early event
 
+    const std::string& getDeviceId() const;
+    
     /**
      * To be called to get the event loop to return
      */
@@ -119,6 +139,31 @@
 
     /** Check status */
     bool ok();
+
+
+    /* *******************************************************************
+     * Methods called by the service constructor to link the
+     * service object and its methods to the Device one, establishing
+     * the eventing and action communication. This is internal and
+     * should not be called by the library user. Not too sure how this
+     * could/should be expressed in C++... */
+    
+    /** Add service to our list. 
+     *
+     * We only ever keep one instance of a serviceId. Multiple calls 
+     * will only keep the last one. This is called from the generic 
+     * UpnpService constructor.
+     */
+    bool addService(UpnpService *);
+    void forgetService(const std::string& serviceId);
+
+    /**
+     * Add mapping from service+action-name to handler function.
+     * This is called by the services implementations during their 
+     * initialization.
+     */
+    void addActionMapping(const UpnpService*,
+                          const std::string& actName, soapfun);
 
 private:
     class Internal;
@@ -135,14 +180,9 @@
 class UpnpService {
 public:
     /**
-     * The main role of the constructor is to register the service action 
-     * callbacks by calling UpnpDevice::addActionMapping()
-     */
-    UpnpService(const std::string& stp,const std::string& sid, UpnpDevice *dev);
-
-    /**
-     * Constructor added to avoid changing the ABI when the noevents
-     * parameter was needed.
+     * The main role of the derived constructor is to register the
+     * service action callbacks by calling UpnpDevice::addActionMapping(). 
+     * The generic constructor registers the object with the device.
      *
      * @param noevents if set, the service will function normally except that
      *                 no calls will be made to libupnp to broadcast events.
@@ -151,7 +191,7 @@
      *                 (in conjunction with a description doc edit).
      */
     UpnpService(const std::string& stp,const std::string& sid,
-                UpnpDevice *dev, bool noevents);
+                const std::string& xmlfn, UpnpDevice *dev, bool noevents=false);
 
     virtual ~UpnpService();
 
@@ -175,6 +215,7 @@
                               std::vector<std::string>& values);
     virtual const std::string& getServiceType() const;
     virtual const std::string& getServiceId() const;
+    virtual const std::string& getXMLFn() const;
 
     /** Get value of the noevents property */
     bool noevents() const;
@@ -214,9 +255,6 @@
     };
 
     
-protected:
-    const std::string m_serviceType;
-    const std::string m_serviceId;
 private:
     class Internal;
     Internal *m;