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