Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package libupnpp for openSUSE:Factory 
checked in at 2021-04-15 16:57:56
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/libupnpp (Old)
 and      /work/SRC/openSUSE:Factory/.libupnpp.new.12324 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "libupnpp"

Thu Apr 15 16:57:56 2021 rev:3 rq:885577 version:0.21.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/libupnpp/libupnpp.changes        2021-01-20 
18:28:50.859596005 +0100
+++ /work/SRC/openSUSE:Factory/.libupnpp.new.12324/libupnpp.changes     
2021-04-15 16:58:40.330793434 +0200
@@ -1,0 +2,11 @@
+Thu Apr 15 00:53:18 UTC 2021 - Ferdinand Thiessen <[email protected]>
+
+- Update to version 0.21.0:
+  * Allow configuring the subscription timeout (init option)
+  * Add interface for the lib to report a subscription autorenewal
+    failure, and to renew all subscriptions.
+  * Add API to set the product/version values in User-Agent
+    and Server headers.
+- Modified description
+
+-------------------------------------------------------------------
@@ -5 +16,2 @@
-  * No upstream changelog available
+  * Content Directory interface: accept responses with 0 TotalMatches.
+  * Adjust excessively noisy messages.

Old:
----
  libupnpp-0.20.2.tar.gz

New:
----
  libupnpp-0.21.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ libupnpp.spec ++++++
--- /var/tmp/diff_new_pack.y8jIJb/_old  2021-04-15 16:58:40.730794067 +0200
+++ /var/tmp/diff_new_pack.y8jIJb/_new  2021-04-15 16:58:40.730794067 +0200
@@ -16,14 +16,14 @@
 #
 
 
-%define so_ver  9
+%define so_ver  11
 Name:           libupnpp
-Version:        0.20.2
+Version:        0.21.0
 Release:        0
-Summary:        Library providing a higher level C++ API over libnpupnp or 
libupnp
+Summary:        Library providing a higher level API over libnpupnp or libupnp
 License:        GPL-2.0-or-later
 Group:          Development/Libraries/C and C++
-URL:            https://www.lesbonscomptes.com/updmpdcli
+URL:            https://www.lesbonscomptes.com/upmpdcli/index.html
 Source0:        
https://www.lesbonscomptes.com/upmpdcli/downloads/libupnpp-%{version}.tar.gz
 BuildRequires:  gcc-c++
 BuildRequires:  pkgconfig
@@ -32,14 +32,24 @@
 BuildRequires:  pkgconfig(libnpupnp)
 
 %description
-Library providing a higher level C++ API over libnpupnp or libupnp
+Libupnpp is a C++ wrapper for libupnp a.k.a Portable UPnP (up to branch 0.17),
+or its C++ descendant, libnpupnp (versions 0.18 and later).
+
+Libupnpp can be used to implement UPnP devices and services, or Control Points.
+The Control Point side of libupnpp, which is documented here,
+allows a C++ program to discover UPnP devices, and exchange commands and 
status with them.
 
 %package -n %{name}%{so_ver}
 Summary:        Library providing a higher level C++ API over libnpupnp or 
libupnp
 Group:          System/Libraries
 
 %description -n %{name}%{so_ver}
-Library providing a higher level C++ API over libnpupnp or libupnp
+Libupnpp is a C++ wrapper for libupnp a.k.a Portable UPnP (up to branch 0.17),
+or its C++ descendant, libnpupnp (versions 0.18 and later).
+
+Libupnpp can be used to implement UPnP devices and services, or Control Points.
+The Control Point side of libupnpp, which is documented here,
+allows a C++ program to discover UPnP devices, and exchange commands and 
status with them.
 
 %package devel
 Summary:        Development files for %{name}
@@ -68,7 +78,7 @@
 %license COPYING
 %{_libdir}/*.so.*
 
-%files -n libupnpp-devel
+%files devel
 %{_includedir}/*
 %{_libdir}/*.so
 %{_libdir}/pkgconfig/*.pc

++++++ libupnpp-0.20.2.tar.gz -> libupnpp-0.21.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/configure 
new/libupnpp-0.21.0/configure
--- old/libupnpp-0.20.2/configure       2020-12-30 15:06:37.000000000 +0100
+++ new/libupnpp-0.21.0/configure       2021-03-13 13:58:07.000000000 +0100
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for libupnpp 0.20.2.
+# Generated by GNU Autoconf 2.69 for libupnpp 0.21.0.
 #
 # Report bugs to <[email protected]>.
 #
@@ -590,8 +590,8 @@
 # Identity of this package.
 PACKAGE_NAME='libupnpp'
 PACKAGE_TARNAME='libupnpp'
-PACKAGE_VERSION='0.20.2'
-PACKAGE_STRING='libupnpp 0.20.2'
+PACKAGE_VERSION='0.21.0'
+PACKAGE_STRING='libupnpp 0.21.0'
 PACKAGE_BUGREPORT='[email protected]'
 PACKAGE_URL='http://www.lesbonscomptes.com/upmpdcli'
 
@@ -1362,7 +1362,7 @@
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures libupnpp 0.20.2 to adapt to many kinds of systems.
+\`configure' configures libupnpp 0.21.0 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1433,7 +1433,7 @@
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of libupnpp 0.20.2:";;
+     short | recursive ) echo "Configuration of libupnpp 0.21.0:";;
    esac
   cat <<\_ACEOF
 
@@ -1559,7 +1559,7 @@
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-libupnpp configure 0.20.2
+libupnpp configure 0.21.0
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2012,7 +2012,7 @@
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by libupnpp $as_me 0.20.2, which was
+It was created by libupnpp $as_me 0.21.0, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -2374,11 +2374,9 @@
 # - If any interfaces have been added since the last public release, then
 #   increment age.
 # - If any interfaces have been removed or changed since the last public
-#   release, then set age to 0 AND CHANGE PACKAGE NAME.
-# libupnpp packages are named libupnppX where X is the .so major number
-# (c-a). This allows packages for multiple incompatible ABIs to be
-# installed
-VERSION_INFO=10:0:1
+#   release, then set age to 0 and change the package name if multiple
+#   versions need to be co-installed.
+VERSION_INFO=11:0:0
 
 
 
@@ -3006,7 +3004,7 @@
 
 # Define the identity of the package.
  PACKAGE='libupnpp'
- VERSION='0.20.2'
+ VERSION='0.21.0'
 
 
 # Some tools Automake needs.
@@ -16930,7 +16928,7 @@
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by libupnpp $as_me 0.20.2, which was
+This file was extended by libupnpp $as_me 0.21.0, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -16997,7 +16995,7 @@
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; 
s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-libupnpp config.status 0.20.2
+libupnpp config.status 0.21.0
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/configure.ac 
new/libupnpp-0.21.0/configure.ac
--- old/libupnpp-0.20.2/configure.ac    2020-12-30 15:05:49.000000000 +0100
+++ new/libupnpp-0.21.0/configure.ac    2021-01-24 18:20:31.000000000 +0100
@@ -4,7 +4,7 @@
 # occur with revision (3rd number) changes.
 
 # !!!! When changing the version, also change the defines in upnpplib.hxx !!!
-AC_INIT([libupnpp], [0.20.2], [[email protected]],
+AC_INIT([libupnpp], [0.21.0], [[email protected]],
              [libupnpp], [http://www.lesbonscomptes.com/upmpdcli])
 
 # Lib version info. See:
@@ -20,11 +20,9 @@
 # - If any interfaces have been added since the last public release, then
 #   increment age.
 # - If any interfaces have been removed or changed since the last public
-#   release, then set age to 0 AND CHANGE PACKAGE NAME. 
-# libupnpp packages are named libupnppX where X is the .so major number
-# (c-a). This allows packages for multiple incompatible ABIs to be
-# installed
-VERSION_INFO=10:0:1
+#   release, then set age to 0 and change the package name if multiple
+#   versions need to be co-installed.
+VERSION_INFO=11:0:0
 
 AC_PREREQ([2.53])
 AC_CONFIG_SRCDIR([libupnpp/upnpplib.hxx])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/base64.cxx 
new/libupnpp-0.21.0/libupnpp/base64.cxx
--- old/libupnpp-0.20.2/libupnpp/base64.cxx     2020-07-01 17:18:31.000000000 
+0200
+++ new/libupnpp-0.21.0/libupnpp/base64.cxx     2021-01-01 16:46:04.000000000 
+0100
@@ -219,7 +219,8 @@
     unsigned char output[4];
 
     out.clear();
-
+    out.reserve(((in.size()) * 4)/3 + 4);
+    
     int srclength = in.length();
     int sidx = 0;
     while (2 < srclength) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/control/mediarenderer.cxx 
new/libupnpp-0.21.0/libupnpp/control/mediarenderer.cxx
--- old/libupnpp-0.20.2/libupnpp/control/mediarenderer.cxx      2020-06-23 
12:43:30.000000000 +0200
+++ new/libupnpp-0.21.0/libupnpp/control/mediarenderer.cxx      2021-02-10 
12:02:25.000000000 +0100
@@ -75,7 +75,7 @@
         (RenderingControl::isRDCService(service.serviceType) ||
          OHProduct::isOHPrService(service.serviceType))
         &&
-        (friendlyName.empty() || !friendlyName.compare(device.friendlyName))) {
+        (friendlyName.empty() || friendlyName == device.friendlyName)) {
         //LOGDEB("MDAccum setting " << device.UDN << endl);
         (*out)[device.UDN] = device;
     }
@@ -87,12 +87,12 @@
 {
     std::unordered_map<string, UPnPDeviceDesc> mydevs;
 
-    UPnPDeviceDirectory::Visitor visitor = bind(MDAccum, &mydevs, friendlyName,
-                                           _1, _2);
+    UPnPDeviceDirectory::Visitor visitor = bind(
+        MDAccum, &mydevs, friendlyName, _1, _2);
     UPnPDeviceDirectory::getTheDir()->traverse(visitor);
-    for (std::unordered_map<string, UPnPDeviceDesc>::iterator it =
-                mydevs.begin(); it != mydevs.end(); it++)
-        devices.push_back(it->second);
+    for (const auto& dev : mydevs) {
+        devices.push_back(dev.second);
+    }
     return !devices.empty();
 }
 
@@ -115,6 +115,35 @@
     return ohpr() ? true : false;
 }
 
+
+#define RESUBS_ONE(S) \
+    do {                                            \
+        auto ptr = (S).lock();                      \
+        if (ptr) {                                  \
+            ok = ptr->reSubscribe();                \
+            if (!ok) {                              \
+                return false;                       \
+            }                                       \
+        }                                           \
+    } while (false);
+
+bool MediaRenderer::reSubscribeAll()
+{
+    bool ok;
+    RESUBS_ONE(m->rdc);
+    RESUBS_ONE(m->avt);
+    RESUBS_ONE(m->cnm);
+    RESUBS_ONE(m->ohpr);
+    RESUBS_ONE(m->ohpl);
+    RESUBS_ONE(m->ohtm);
+    RESUBS_ONE(m->ohvl);
+    RESUBS_ONE(m->ohrc);
+    RESUBS_ONE(m->ohrd);
+    RESUBS_ONE(m->ohif);
+    RESUBS_ONE(m->ohsn);
+    return true;
+}
+
 RDCH MediaRenderer::rdc()
 {
     if (desc() == 0)
@@ -123,10 +152,9 @@
     RDCH rdcl = m->rdc.lock();
     if (rdcl)
         return rdcl;
-    for (vector<UPnPServiceDesc>::const_iterator it = desc()->services.begin();
-            it != desc()->services.end(); it++) {
-        if (RenderingControl::isRDCService(it->serviceType)) {
-            rdcl = RDCH(new RenderingControl(*desc(), *it));
+    for (const auto& service : desc()->services) {
+        if (RenderingControl::isRDCService(service.serviceType)) {
+            rdcl = RDCH(new RenderingControl(*desc(), service));
             break;
         }
     }
@@ -141,10 +169,9 @@
     AVTH avtl = m->avt.lock();
     if (avtl)
         return avtl;
-    for (vector<UPnPServiceDesc>::const_iterator it = desc()->services.begin();
-            it != desc()->services.end(); it++) {
-        if (AVTransport::isAVTService(it->serviceType)) {
-            avtl = AVTH(new AVTransport(*desc(), *it));
+    for (const auto& service : desc()->services) {
+        if (AVTransport::isAVTService(service.serviceType)) {
+            avtl = AVTH(new AVTransport(*desc(), service));
             break;
         }
     }
@@ -177,10 +204,9 @@
     OHPRH ohprl = m->ohpr.lock();
     if (ohprl)
         return ohprl;
-    for (vector<UPnPServiceDesc>::const_iterator it = desc()->services.begin();
-            it != desc()->services.end(); it++) {
-        if (OHProduct::isOHPrService(it->serviceType)) {
-            ohprl = OHPRH(new OHProduct(*desc(), *it));
+    for (const auto& service : desc()->services) {
+        if (OHProduct::isOHPrService(service.serviceType)) {
+            ohprl = OHPRH(new OHProduct(*desc(), service));
             break;
         }
     }
@@ -195,10 +221,9 @@
     OHPLH ohpll = m->ohpl.lock();
     if (ohpll)
         return ohpll;
-    for (vector<UPnPServiceDesc>::const_iterator it = desc()->services.begin();
-            it != desc()->services.end(); it++) {
-        if (OHPlaylist::isOHPlService(it->serviceType)) {
-            ohpll = OHPLH(new OHPlaylist(*desc(), *it));
+    for (const auto& service : desc()->services) {
+        if (OHPlaylist::isOHPlService(service.serviceType)) {
+            ohpll = OHPLH(new OHPlaylist(*desc(), service));
             break;
         }
     }
@@ -213,10 +238,9 @@
     OHRCH ohrcl = m->ohrc.lock();
     if (ohrcl)
         return ohrcl;
-    for (vector<UPnPServiceDesc>::const_iterator it = desc()->services.begin();
-            it != desc()->services.end(); it++) {
-        if (OHReceiver::isOHRcService(it->serviceType)) {
-            ohrcl = OHRCH(new OHReceiver(*desc(), *it));
+    for (const auto& service : desc()->services) {
+        if (OHReceiver::isOHRcService(service.serviceType)) {
+            ohrcl = OHRCH(new OHReceiver(*desc(), service));
             break;
         }
     }
@@ -231,10 +255,9 @@
     OHRDH handle = m->ohrd.lock();
     if (handle)
         return handle;
-    for (vector<UPnPServiceDesc>::const_iterator it = desc()->services.begin();
-            it != desc()->services.end(); it++) {
-        if (OHRadio::isOHRdService(it->serviceType)) {
-            handle = OHRDH(new OHRadio(*desc(), *it));
+    for (const auto& service : desc()->services) {
+        if (OHRadio::isOHRdService(service.serviceType)) {
+            handle = OHRDH(new OHRadio(*desc(), service));
             break;
         }
     }
@@ -249,10 +272,9 @@
     OHIFH handle = m->ohif.lock();
     if (handle)
         return handle;
-    for (vector<UPnPServiceDesc>::const_iterator it = desc()->services.begin();
-            it != desc()->services.end(); it++) {
-        if (OHInfo::isOHInfoService(it->serviceType)) {
-            handle = OHIFH(new OHInfo(*desc(), *it));
+    for (const auto& service : desc()->services) {
+        if (OHInfo::isOHInfoService(service.serviceType)) {
+            handle = OHIFH(new OHInfo(*desc(), service));
             break;
         }
     }
@@ -267,10 +289,9 @@
     OHSNH handle = m->ohsn.lock();
     if (handle)
         return handle;
-    for (vector<UPnPServiceDesc>::const_iterator it = desc()->services.begin();
-            it != desc()->services.end(); it++) {
-        if (OHSender::isOHSenderService(it->serviceType)) {
-            handle = OHSNH(new OHSender(*desc(), *it));
+    for (const auto& service : desc()->services) {
+        if (OHSender::isOHSenderService(service.serviceType)) {
+            handle = OHSNH(new OHSender(*desc(), service));
             break;
         }
     }
@@ -285,10 +306,9 @@
     OHTMH ohtml = m->ohtm.lock();
     if (ohtml)
         return ohtml;
-    for (vector<UPnPServiceDesc>::const_iterator it = desc()->services.begin();
-            it != desc()->services.end(); it++) {
-        if (OHTime::isOHTMService(it->serviceType)) {
-            ohtml = OHTMH(new OHTime(*desc(), *it));
+    for (const auto& service : desc()->services) {
+        if (OHTime::isOHTMService(service.serviceType)) {
+            ohtml = OHTMH(new OHTime(*desc(), service));
             break;
         }
     }
@@ -303,10 +323,9 @@
     OHVLH ohvll = m->ohvl.lock();
     if (ohvll)
         return ohvll;
-    for (vector<UPnPServiceDesc>::const_iterator it = desc()->services.begin();
-            it != desc()->services.end(); it++) {
-        if (OHVolume::isOHVLService(it->serviceType)) {
-            ohvll = OHVLH(new OHVolume(*desc(), *it));
+    for (const auto& service : desc()->services) {
+        if (OHVolume::isOHVLService(service.serviceType)) {
+            ohvll = OHVLH(new OHVolume(*desc(), service));
             break;
         }
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/control/mediarenderer.hxx 
new/libupnpp-0.21.0/libupnpp/control/mediarenderer.hxx
--- old/libupnpp-0.20.2/libupnpp/control/mediarenderer.hxx      2020-09-21 
09:28:17.000000000 +0200
+++ new/libupnpp-0.21.0/libupnpp/control/mediarenderer.hxx      2021-01-02 
10:40:11.000000000 +0100
@@ -78,6 +78,8 @@
                                const std::string& friendlyName = "");
     static bool isMRDevice(const std::string& devicetype);
 
+    bool reSubscribeAll();
+    
 protected:
     static const std::string DType;
 private:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/control/ohinfo.cxx 
new/libupnpp-0.21.0/libupnpp/control/ohinfo.cxx
--- old/libupnpp-0.20.2/libupnpp/control/ohinfo.cxx     2020-11-26 
09:26:06.000000000 +0100
+++ new/libupnpp-0.21.0/libupnpp/control/ohinfo.cxx     2021-01-12 
10:02:23.000000000 +0100
@@ -92,7 +92,7 @@
     }
     string didl;
     if (!data.get("Value", &didl)) {
-        LOGERR("OHInfo::Read: missing Value in response" << endl);
+        LOGERR("OHInfo::metatext: missing Value in response" << endl);
         return UPNP_E_BAD_RESPONSE;
     }
     if (didl.empty()) {
@@ -102,4 +102,119 @@
     return OHRadio::decodeMetadata("OHInfo::metatext", didl, dirent);
 }
 
+int OHInfo::track(std::string *uri, UPnPDirObject *dirent)
+{
+    SoapOutgoing args(getServiceType(), "Counters");
+    SoapIncoming data;
+    int ret = runAction(args, data);
+    if (ret != UPNP_E_SUCCESS) {
+        LOGERR("OHInfo::counters: runAction failed\n");
+        return ret;
+    }
+    if (uri) {
+        if (!data.get("Uri", uri)) {
+            LOGERR("OHInfo::track: missing Uri in response" << endl);
+            return UPNP_E_BAD_RESPONSE;
+        }
+    }
+    if (dirent) {
+        string didl;
+        if (!data.get("Metadata", &didl)) {
+            LOGERR("OHInfo::track: missing Metadata in response" << endl);
+            return UPNP_E_BAD_RESPONSE;
+        }
+        return OHRadio::decodeMetadata("OHInfo::metatext", didl, dirent);
+    }
+    return UPNP_E_SUCCESS;
+}
+
+int OHInfo::counters(int *trackcount, int *detailscount, int *metatextcount)
+{
+    SoapOutgoing args(getServiceType(), "Counters");
+    SoapIncoming data;
+    int ret = runAction(args, data);
+    if (ret != UPNP_E_SUCCESS) {
+        LOGERR("OHInfo::counters: runAction failed\n");
+        return ret;
+    }
+    if (trackcount) {
+        const char *nm = "TrackCount";
+        if (!data.get(nm, trackcount)) {
+            LOGERR("OHInfo::counters: missing " << nm << " in response" << 
endl);
+            return UPNP_E_BAD_RESPONSE;
+        }
+    }
+    if (detailscount) {
+        const char *nm = "DetailsCount";
+        if (!data.get(nm, detailscount)) {
+            LOGERR("OHInfo::counters: missing " << nm << " in response" << 
endl);
+            return UPNP_E_BAD_RESPONSE;
+        }
+    }
+    if (metatextcount) {
+        const char *nm = "MetatextCount";
+        if (!data.get(nm, metatextcount)) {
+            LOGERR("OHInfo::counters: missing " << nm << " in response" << 
endl);
+            return UPNP_E_BAD_RESPONSE;
+        }
+    }
+    return UPNP_E_SUCCESS;
+}
+
+
+int OHInfo::details(int *duration, int *bitrate, int *bitdepth, int 
*samplerate, bool *lossless,
+                    std::string *codecname)
+{
+    SoapOutgoing args(getServiceType(), "Details");
+    SoapIncoming data;
+    int ret = runAction(args, data);
+    if (ret != UPNP_E_SUCCESS) {
+        LOGERR("OHInfo::details: runAction failed\n");
+        return ret;
+    }
+    if (duration) {
+        const char *nm = "Duration";
+        if (!data.get(nm, duration)) {
+            LOGERR("OHInfo::counters: missing " << nm << " in response" << 
endl);
+            return UPNP_E_BAD_RESPONSE;
+        }
+    }
+    if (bitrate) {
+        const char *nm = "BitRate";
+        if (!data.get(nm, bitrate)) {
+            LOGERR("OHInfo::counters: missing " << nm << " in response" << 
endl);
+            return UPNP_E_BAD_RESPONSE;
+        }
+    }
+    if (bitdepth) {
+        const char *nm = "BitDepth";
+        if (!data.get(nm, bitdepth)) {
+            LOGERR("OHInfo::counters: missing " << nm << " in response" << 
endl);
+            return UPNP_E_BAD_RESPONSE;
+        }
+    }
+    if (samplerate) {
+        const char *nm = "SampleRate";
+        if (!data.get(nm, samplerate)) {
+            LOGERR("OHInfo::counters: missing " << nm << " in response" << 
endl);
+            return UPNP_E_BAD_RESPONSE;
+        }
+    }
+    if (lossless) {
+        const char *nm = "Lossless";
+        if (!data.get(nm, lossless)) {
+            LOGERR("OHInfo::counters: missing " << nm << " in response" << 
endl);
+            return UPNP_E_BAD_RESPONSE;
+        }
+    }
+    if (codecname) {
+        const char *nm = "Codecname";
+        if (!data.get(nm, codecname)) {
+            LOGERR("OHInfo::counters: missing " << nm << " in response" << 
endl);
+            return UPNP_E_BAD_RESPONSE;
+        }
+    }
+    return UPNP_E_SUCCESS;
+}
+
 } // End namespace UPnPClient
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/control/ohinfo.hxx 
new/libupnpp-0.21.0/libupnpp/control/ohinfo.hxx
--- old/libupnpp-0.20.2/libupnpp/control/ohinfo.hxx     2020-09-21 
09:29:22.000000000 +0200
+++ new/libupnpp-0.21.0/libupnpp/control/ohinfo.hxx     2021-01-13 
08:57:55.000000000 +0100
@@ -51,6 +51,10 @@
     static bool isOHInfoService(const std::string& st);
     virtual bool serviceTypeMatch(const std::string& tp);
 
+    int counters(int *trackcount, int *detailscount, int *metatextcount);
+    int track(std::string *uri, UPnPDirObject *dirent);
+    int details(int *duration, int *bitrate, int *bitdepth, int *samplerate,
+                bool *lossless, std::string *codecname);
     int metatext(UPnPDirObject *dirent);
 
 protected:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/control/ohplaylist.cxx 
new/libupnpp-0.21.0/libupnpp/control/ohplaylist.cxx
--- old/libupnpp-0.20.2/libupnpp/control/ohplaylist.cxx 2020-07-28 
13:25:22.000000000 +0200
+++ new/libupnpp-0.21.0/libupnpp/control/ohplaylist.cxx 2021-01-03 
14:26:37.000000000 +0100
@@ -80,44 +80,48 @@
     const std::unordered_map<std::string, std::string>& props)
 {
     LOGDEB1("OHPlaylist::evtCallback: getReporter(): " << getReporter() << 
endl);
-    for (std::unordered_map<std::string, std::string>::const_iterator it =
-                props.begin(); it != props.end(); it++) {
-        if (!getReporter()) {
-            LOGDEB1("OHPlaylist::evtCallback: " << it->first << " -> "
-                    << it->second << endl);
+    auto reporter = getReporter();
+    if (reporter && props.empty()) {
+        // Subscription renewal failed
+        reporter->autorenew_failed();
+        return;
+    }
+    for (const auto& prop : props) {
+        if (!reporter) {
+            // For logging with no reporter set
+            LOGDEB1("OHPlaylist::evtCallback: " << prop.first << " -> "
+                    << prop.second << endl);
             continue;
         }
 
-        if (!it->first.compare("TransportState")) {
+        if (prop.first == "TransportState") {
             TPState tp;
-            stringToTpState(it->second, &tp);
-            getReporter()->changed(it->first.c_str(), int(tp));
+            stringToTpState(prop.second, &tp);
+            getReporter()->changed(prop.first.c_str(), int(tp));
 
-        } else if (!it->first.compare("ProtocolInfo")) {
-            getReporter()->changed(it->first.c_str(),
-                                   it->second.c_str());
+        } else if (prop.first == "ProtocolInfo") {
+            getReporter()->changed(prop.first.c_str(),
+                                   prop.second.c_str());
 
-        } else if (!it->first.compare("Repeat") ||
-                   !it->first.compare("Shuffle")) {
+        } else if (prop.first == "Repeat" || prop.first == "Shuffle") {
             bool val = false;
-            stringToBool(it->second, &val);
-            getReporter()->changed(it->first.c_str(), val ? 1 : 0);
+            stringToBool(prop.second, &val);
+            getReporter()->changed(prop.first.c_str(), val ? 1 : 0);
 
-        } else if (!it->first.compare("Id") ||
-                   !it->first.compare("TracksMax")) {
-            getReporter()->changed(it->first.c_str(),
-                                   atoi(it->second.c_str()));
+        } else if (prop.first == "Id" || prop.first == "TracksMax") {
+            getReporter()->changed(prop.first.c_str(),
+                                   atoi(prop.second.c_str()));
 
-        } else if (!it->first.compare("IdArray")) {
+        } else if (prop.first == "IdArray") {
             // Decode IdArray. See how we call the client
             vector<int> v;
-            ohplIdArrayToVec(it->second, &v);
-            getReporter()->changed(it->first.c_str(), v);
+            ohplIdArrayToVec(prop.second, &v);
+            getReporter()->changed(prop.first.c_str(), v);
 
         } else {
             LOGERR("OHPlaylist event: unknown variable: name [" <<
-                   it->first << "] value [" << it->second << endl);
-            getReporter()->changed(it->first.c_str(), it->second.c_str());
+                   prop.first << "] value [" << prop.second << endl);
+            getReporter()->changed(prop.first.c_str(), prop.second.c_str());
         }
     }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/control/service.cxx 
new/libupnpp-0.21.0/libupnpp/control/service.cxx
--- old/libupnpp-0.20.2/libupnpp/control/service.cxx    2020-07-28 
13:25:22.000000000 +0200
+++ new/libupnpp-0.21.0/libupnpp/control/service.cxx    2021-01-03 
14:21:22.000000000 +0100
@@ -77,10 +77,10 @@
  * libupnp to call the appropriate object method when it receives
  * an event. */
 static std::unordered_map<std::string, evtCBFunc> o_calls;
+static std::mutex cblock;
 
 
-Service::Service(const UPnPDeviceDesc& devdesc,
-                 const UPnPServiceDesc& servdesc)
+Service::Service(const UPnPDeviceDesc& devdesc, const UPnPServiceDesc& 
servdesc)
 {
     if ((m = new Internal()) == 0) {
         LOGERR("Device::Device: out of memory" << endl);
@@ -225,41 +225,38 @@
     return runAction(args, data);
 }
 
-static std::mutex cblock;
 // The static event callback given to libupnp
 static int srvCB(Upnp_EventType et, CBCONST void* vevp, void*)
 {
     std::unique_lock<std::mutex> lock(cblock);
 
-    LOGDEB0("Service:srvCB: " << evTypeAsString(et) << endl);
+    // All event types begin with a SID field
+    const char *sid = UpnpEvent_get_SID_cstr((UpnpEvent*)vevp);
+    
+    LOGDEB0("Service:srvCB: " << evTypeAsString(et) << " SID " << sid << endl);
+
+    auto it = o_calls.find(sid);
 
     switch (et) {
-    case UPNP_EVENT_RENEWAL_COMPLETE:
-    case UPNP_EVENT_SUBSCRIBE_COMPLETE:
-    case UPNP_EVENT_UNSUBSCRIBE_COMPLETE:
     case UPNP_EVENT_AUTORENEWAL_FAILED:
     {
-        const char *ff = (const char *)vevp;
-        (void)ff;
-        LOGDEB1("Service:srvCB: subs event: " << ff << endl);
+        if (it != o_calls.end()) {
+            it->second({});
+        }
         break;
     }
-
     case UPNP_EVENT_RECEIVED:
     {
         UpnpEvent *evp = (UpnpEvent *)vevp;
-        LOGDEB1("Service:srvCB: var change event: SID " <<
-                UpnpEvent_get_SID_cstr(evp) << " EventKey " <<
-                UpnpEvent_get_EventKey(evp) << " changed " <<
+        LOGDEB1("Service:srvCB: var change event: SID " << sid <<
+                " EventKey " << UpnpEvent_get_EventKey(evp) << " changed " <<
                 SoapHelp::argsToString(evp->ChangedVariables.begin(),
-                                       evp->ChangedVariables.end()) << endl);
+                                       evp->ChangedVariables.end()) << "\n");
 
-        auto it = o_calls.find(UpnpEvent_get_SID_cstr(evp));
         if (it != o_calls.end()) {
             (it->second)(evp->ChangedVariables);
         } else {
-            LOGINF("Service::srvCB: no callback found for sid " <<
-                   UpnpEvent_get_SID_cstr(evp) << endl);
+            LOGINF("Service::srvCB: no callback found for SID " << sid << 
"\n");
         }
         break;
     }
@@ -267,7 +264,7 @@
     default:
         // Ignore other events for now
         LOGDEB("Service:srvCB: unprocessed evt type: [" <<
-               evTypeAsString(et) << "]"  << endl);
+               evTypeAsString(et) << "]"  << "\n");
         break;
     }
 
@@ -292,9 +289,6 @@
         LOGERR("Service::initEvents: Can't get lib" << endl);
         return false;
     }
-    lib->m->registerHandler(UPNP_EVENT_RENEWAL_COMPLETE, srvCB, 0);
-    lib->m->registerHandler(UPNP_EVENT_SUBSCRIBE_COMPLETE, srvCB, 0);
-    lib->m->registerHandler(UPNP_EVENT_UNSUBSCRIBE_COMPLETE, srvCB, 0);
     lib->m->registerHandler(UPNP_EVENT_AUTORENEWAL_FAILED, srvCB, 0);
     lib->m->registerHandler(UPNP_EVENT_RECEIVED, srvCB, 0);
     return true;
@@ -308,7 +302,7 @@
         LOGINF("Service::subscribe: no lib" << endl);
         return false;
     }
-    int timeout = 1800;
+    int timeout = lib->m->getSubsTimeout();
     int ret = UpnpSubscribe(lib->m->getclh(), eventURL.c_str(),
                             &timeout, SID);
     if (ret != UPNP_E_SUCCESS) {
@@ -341,14 +335,17 @@
     return true;
 }
 
-void Service::registerCallback(evtCBFunc c)
+bool Service::registerCallback(evtCBFunc c)
 {
-    if (!m || !m->subscribe())
-        return;
+    if (!m || !m->subscribe()) {
+        LOGERR("registerCallback: subscribe failed\n");
+        return false;
+    }
     std::unique_lock<std::mutex> lock(cblock);
     LOGDEB1("Service::registerCallback: " << m->eventURL << " SID " <<
             m->SID << endl);
     o_calls[m->SID] = c;
+    return true;
 }
 
 void Service::unregisterCallback()
@@ -380,12 +377,12 @@
     m->reporter = reporter;
 }
 
-void Service::reSubscribe()
+bool Service::reSubscribe()
 {
     LOGDEB("Service::reSubscribe()\n");
     if (m->SID[0] == 0) {
         LOGINF("Service::reSubscribe: no subscription (null SID)\n");
-        return;
+        return false;
     }
     evtCBFunc c;
     {
@@ -394,12 +391,13 @@
         if (it == o_calls.end()) {
             LOGINF("Service::reSubscribe: no callback found for m->SID " <<
                    m->SID << endl);
-            return;
+            return false;
         }
         c = it->second;
     }
     unregisterCallback();
     registerCallback(c);
+    return true;
 }
 
 template int Service::runSimpleAction<int>(string const&, string const&, int);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/control/service.hxx 
new/libupnpp-0.21.0/libupnpp/control/service.hxx
--- old/libupnpp-0.20.2/libupnpp/control/service.hxx    2020-09-21 
09:33:30.000000000 +0200
+++ new/libupnpp-0.21.0/libupnpp/control/service.hxx    2021-01-03 
18:35:45.000000000 +0100
@@ -41,10 +41,16 @@
  * Runs in an event thread. This could for example be
  * implemented by a Qt Object to generate events for the GUI.
  *
- * The Service class does a bit of parsing for common cases. 
+ * This is used by all "precooked" UPnP/AV service classes in the
+   library. The derived Service class does a bit of parsing for common
+   cases.
  * The different methods cover all current types of audio UPnP
  * state variable data I am aware of. Of course, other types of data can 
  * be reported as a character string, leaving the parsing to the client code.
+ *
+ * In the general case, you could also derive from Service, implement
+ * and install an evtCBFunc callback and not use VarEventReporter at
+ * all.
  */
 class UPNPP_API VarEventReporter {
 public:
@@ -58,12 +64,16 @@
     virtual void changed(const char * /*nm*/, UPnPDirObject /*meta*/) {}
     /** Special for  ohplaylist. Not always needed */
     virtual void changed(const char * /*nm*/, std::vector<int> /*ids*/) {}
+    /** Subscription autorenew failed. You may want to schedule a
+       resubscribe() later. */
+    virtual void autorenew_failed() {}
 };
 
 /** Type of the event callbacks. 
  * If registered by a call to Service::registerCallBack(cbfunc), this will be
  * called with a map of state variable names and values when 
  * an event arrives. The call is performed in a separate thread. 
+ * A call with an empty map means that a subscription autorenew failed.
  */
 typedef
 std::function<void (const std::unordered_map<std::string, std::string>&)>
@@ -92,8 +102,9 @@
     
     /** Restart the subscription to get all the State variable values,
      * in case we get the events before we are ready (e.g. before the
-     * connections are set in a qt app) */
-    virtual void reSubscribe();
+     * connections are set in a qt app). Also: when reconnecting after
+     * a device restarts. */
+    virtual bool reSubscribe();
 
     /** Accessors for the values extracted from the device description during 
      *  initialization */
@@ -136,7 +147,10 @@
      */
     virtual VarEventReporter *getReporter();
 
-    /** Install event data reporter object */
+    /** Install or uninstall event data reporter object. 
+     *  @param  reporter the callbacks to be installed, or nullptr 
+     *  to disable reporting (and cancel the upnp subscription). 
+     */
     virtual void installReporter(VarEventReporter* reporter);
 
     /** Perform a comparison to the service type string for this specific 
@@ -166,7 +180,7 @@
      * creates an entry in the static map, using m_SID, which was
      * obtained by subscribe() during construction
      */
-    void registerCallback(evtCBFunc c);
+    bool registerCallback(evtCBFunc c);
 
     /** To be overridden in classes which actually support events. Will be
      * called by installReporter(). The call sequence is as follows:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/device/device.cxx 
new/libupnpp-0.21.0/libupnpp/device/device.cxx
--- old/libupnpp-0.20.2/libupnpp/device/device.cxx      2020-12-07 
17:57:33.000000000 +0100
+++ new/libupnpp-0.21.0/libupnpp/device/device.cxx      2021-02-09 
16:01:04.000000000 +0100
@@ -41,6 +41,12 @@
 #include "libupnpp/upnpp_p.hxx"
 #include "vdir.hxx"
 
+#ifndef NPUPNP_AT_LEAST
+#define NPUPNP_AT_LEAST(MAJ,MIN,REV)                                  \
+    VERSION_AT_LEAST(NPUPNP_VERSION_MAJOR,NPUPNP_VERSION_MINOR,       \
+                     NPUPNP_VERSION_PATCH, (MAJ),(MIN),(REV))
+#endif
+
 using namespace std;
 using namespace UPnPP;
 
@@ -71,6 +77,10 @@
     UPnPP::LibUPnP *lib{nullptr};
     string deviceId;
 
+    // For SERVER headers:
+    string product;
+    string version;
+    
     // In case startloop has been called: the event loop thread.
     std::thread loopthread;
 
@@ -208,18 +218,29 @@
 
 UpnpDevice::~UpnpDevice()
 {
-    shouldExit();
-    if (m->loopthread.joinable())
-        m->loopthread.join();
+    m->needExit = true;
+    m->evloopcond.notify_all();
 
-    if (nullptr != m->rootdev) {
+    if (nullptr == m->rootdev) {
         UpnpUnRegisterRootDevice(m->dvh);
     }
 
+    if (m->loopthread.joinable())
+        m->loopthread.join();
+
     std::unique_lock<std::mutex> lock(o->devices_lock);
     auto it = o->devices.find(m->deviceId);
     if (it != o->devices.end())
         o->devices.erase(it);
+    delete m;
+}
+
+void UpnpDevice::setProductVersion(const char *product, const char *version)
+{
+    if (product && version) {
+        m->product = product;
+        m->version = version;
+    }
 }
 
 const string& UpnpDevice::getDeviceId() const
@@ -315,6 +336,11 @@
         return false;
     }
 #endif
+#if NPUPNP_AT_LEAST(4,1,0)
+    if (!product.empty()) {
+        UpnpDeviceSetProduct(dvh, product.c_str(), version.c_str());
+    }
+#endif
     if ((ret = UpnpSendAdvertisement(dvh, expiretime)) != 0) {
         LOGERR("UpnpDevice::Internal::start(): sendAvertisement failed: " <<
                lib->errAsString("UpnpDevice: UpnpSendAdvertisement", ret) <<
@@ -757,6 +783,9 @@
 
 void UpnpDevice::shouldExit()
 {
+    if (nullptr == m->rootdev) {
+        UpnpUnRegisterRootDevice(m->dvh);
+    }
     m->needExit = true;
     m->evloopcond.notify_all();
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/device/device.hxx 
new/libupnpp-0.21.0/libupnpp/device/device.hxx
--- old/libupnpp-0.20.2/libupnpp/device/device.hxx      2020-11-07 
14:25:04.000000000 +0100
+++ new/libupnpp-0.21.0/libupnpp/device/device.hxx      2021-01-24 
18:49:36.000000000 +0100
@@ -39,8 +39,17 @@
 /** 
  * Base Device class.
  * 
- * The derived class mostly need to implement the readLibFile() method for 
- * retrieving misc XML description fragments and files.
+ * This class dispatches the UPnP action calls to the appropriate
+ * UpnpService derived class methods, and receives, then forwards, the
+ * UPnP events. 
+ *
+ * The UpnpService objects attach themselves to the
+ * UpnpDevice object during their construction and do most of the
+ * application work.
+ *
+ * The derived UPnPDevice classes mostly need to implement the
+ * readLibFile() method for retrieving misc XML description fragments
+ * and files.
  */
 class UPNPP_API UpnpDevice {
 public:
@@ -65,7 +74,11 @@
      */
     UpnpDevice(UpnpDevice *rootdev, const std::string& deviceId);
     
-    ~UpnpDevice();
+    virtual ~UpnpDevice();
+
+     /** Set the product name and version to be used in SERVER headers. 
+      * If not set, the library default will be used */
+    void setProductVersion(const char *product, const char *version);
 
     /** Retrieve the network endpoint the server is listening on */
     static bool ipv4(std::string *host, unsigned short *port);
@@ -127,28 +140,33 @@
     /**
      * Event-generating loop. 
      *
-     * This can either be called from the main thread, or in a
-     * separate thread by a call to startloop
-     *
-     * This loop mostly polls the derived class getEventData() method
-     * and generates an UPnP event if it returns changed variables. 
+     * This only returns if shouldExit() is called from another
+     * thread. eventloop() can be called from an application thread
+     * (maybe the main one when program initialisation is
+     * done). Alternatively, it can be entered through startloop(),
+     * which will create a thread to run it.
+     *
+     * The loop runs every second to call the getEventData() methods
+     * from the attached UpnpService objects and generates UPnP events
+     * if they return changed variables.
      * 
      * The UPnP action calls happen in other (npupnp) threads with
      * which we synchronize, currently using a global lock.
      *
      * Alternatively to running this method, either directly or through 
-     * startloop, it is possible to initially call start() (which returns 
+     * startloop(), it is possible to initially call start() (which returns 
      * after initializing the device with the lower level library), and then 
-     * call notifyEvent() from the application own event loop.
+     * call notifyEvent() when needed, from the application own event
+     * loop. No polling occurs in this case, events are pushed from
+     * the application code.
      */
     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.
+     * This is an alternative to running eventloop() from an application
+     * thread. The destructor will take care of the internal thread.
      */
     void startloop();
 
@@ -171,7 +189,7 @@
     /** 
      * Register and activate this device.
      *
-     * This should only be called if eventloop() is not used, and any events
+     * This should only be called if eventloop() is not used, and all events
      * will be generated by calls to notifyEvent().
      */
     bool start();
@@ -180,8 +198,11 @@
      * Generate an event for the service.
      * 
      * This is mostly useful if eventloop() is not used.
+     * @param service the service generating the event. 
+     * @param names the names of changed variables.
+     * @param values the parallel values of the changed variables.
      */
-    void notifyEvent(const UpnpService*,
+    void notifyEvent(const UpnpService *service,
                      const std::vector<std::string>& names,
                      const std::vector<std::string>& values);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/device/vdir.cxx 
new/libupnpp-0.21.0/libupnpp/device/vdir.cxx
--- old/libupnpp-0.20.2/libupnpp/device/vdir.cxx        2020-09-20 
14:05:25.000000000 +0200
+++ new/libupnpp-0.21.0/libupnpp/device/vdir.cxx        2020-12-31 
17:21:30.000000000 +0100
@@ -25,6 +25,7 @@
 #include <iostream>
 #include <utility>
 #include <unordered_map>
+#include <mutex>
 
 #include "libupnpp/log.hxx"
 #include "libupnpp/upnpp_p.hxx"
@@ -53,6 +54,7 @@
     VirtualDir::FileOps ops;
 };
 static unordered_map<string,  DirEnt> m_dirs;
+static std::mutex dirsmutex;
 
 static void pathcatslash(string& path)
 {
@@ -61,7 +63,7 @@
     }
 }
 
-// Look up entry for pathname
+// Look up entry for pathname. Call with lock held
 static FileEnt *vdgetentry(const char *pathname, DirEnt **de)
 {
     //LOGDEB("vdgetentry: [" << pathname << "]" << endl);
@@ -103,6 +105,7 @@
     
     //LOGDEB("VirtualDir::addFile: path " << path << " name " << name << endl);
 
+    std::lock_guard<std::mutex> lock(dirsmutex);
     if (m_dirs.find(path) == m_dirs.end()) {
         m_dirs[path] = DirEnt();
         UpnpAddVirtualDir(path.c_str(), 0, 0);
@@ -122,6 +125,7 @@
 {
     string path(_path);
     pathcatslash(path);
+    std::lock_guard<std::mutex> lock(dirsmutex);
     if (m_dirs.find(path) == m_dirs.end()) {
         m_dirs[path] = DirEnt(true);
         UpnpAddVirtualDir(path.c_str(), 0, 0);
@@ -159,6 +163,7 @@
 {
     //LOGDEB("vdgetinfo: [" << fn << "] off_t " << sizeof(off_t) <<
     // " time_t " << sizeof(time_t) << endl);
+    std::lock_guard<std::mutex> lock(dirsmutex);
     DirEnt *dir;
     FileEnt *entry = vdgetentry(fn, &dir);
     if (dir && dir->isvd) {
@@ -191,6 +196,7 @@
     const char* fn, enum UpnpOpenFileMode, const void*, const void*)
 {
     //LOGDEB("vdopen: " << fn << endl);
+    std::lock_guard<std::mutex> lock(dirsmutex);
     DirEnt *dir;
     FileEnt *entry = vdgetentry(fn, &dir);
 
@@ -214,6 +220,7 @@
                   const void*, const void*)
 {
     // LOGDEB("vdread: " << endl);
+    std::lock_guard<std::mutex> lock(dirsmutex);
     if (buflen == 0) {
         return 0;
     }
@@ -236,6 +243,7 @@
                   const void*, const void*)
 {
     // LOGDEB("vdseek: " << endl);
+    std::lock_guard<std::mutex> lock(dirsmutex);
     Handle *h = (Handle *)fileHnd;
     if (h->vhandle) {
         return h->dir->ops.seek(h->vhandle, (off_t)offset, origin) ==
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/log.cpp 
new/libupnpp-0.21.0/libupnpp/log.cpp
--- old/libupnpp-0.20.2/libupnpp/log.cpp        2020-09-21 10:23:26.000000000 
+0200
+++ new/libupnpp-0.21.0/libupnpp/log.cpp        2020-11-19 11:10:07.000000000 
+0100
@@ -44,7 +44,7 @@
     if (!m_tocerr && m_stream.is_open()) {
         m_stream.close();
     }
-    if (!m_fn.empty() && m_fn.compare("stderr")) {
+    if (!m_fn.empty() && m_fn != "stderr") {
         m_stream.open(m_fn, std::fstream::out | std::ofstream::trunc);
         if (!m_stream.is_open()) {
             cerr << "Logger::Logger: log open failed: for [" <<
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/log.h 
new/libupnpp-0.21.0/libupnpp/log.h
--- old/libupnpp-0.20.2/libupnpp/log.h  2020-06-29 10:01:51.000000000 +0200
+++ new/libupnpp-0.21.0/libupnpp/log.h  2020-11-19 11:10:07.000000000 +0100
@@ -48,7 +48,7 @@
      * output. Creates the singleton logger object. Only the first
      * call changes the state, further ones just return the Logger
      * pointer. */
-    static Logger *getTheLog(const std::string& fn);
+    static Logger *getTheLog(const std::string& fn = std::string());
 
     /** Close and reopen the output file. For rotating the log: rename
      * then reopen. */
@@ -80,6 +80,14 @@
     int getloglevel() const {
         return m_loglevel;
     }
+    /** @brief Retrieve current log file name */
+    const std::string& getlogfilename() const {
+        return m_fn;
+    }
+    /** @brief Logging to stderr ? */
+    bool logisstderr() const {
+        return m_tocerr;
+    }
 
     /** @brief turn date logging on or off (default is off) */
     void logthedate(bool onoff) {
@@ -121,11 +129,11 @@
     Logger& operator=(const Logger &);
 };
 
-#define LOGGER_PRT (Logger::getTheLog("")->getstream())
+#define LOGGER_PRT (Logger::getTheLog()->getstream())
 
 #if LOGGER_THREADSAFE
 #define LOGGER_LOCK                                                     \
-    std::unique_lock<std::recursive_mutex> 
lock(Logger::getTheLog("")->getmutex())
+    std::unique_lock<std::recursive_mutex> 
lock(Logger::getTheLog()->getmutex())
 #else
 #define LOGGER_LOCK
 #endif
@@ -134,11 +142,11 @@
 #define LOGGER_LOCAL_LOGINC 0
 #endif
 
-#define LOGGER_LEVEL (Logger::getTheLog("")->getloglevel() +    \
+#define LOGGER_LEVEL (Logger::getTheLog()->getloglevel() +    \
                       LOGGER_LOCAL_LOGINC)
 
-#define LOGGER_DATE (Logger::getTheLog("")->loggingdate() ? \
-                     Logger::getTheLog("")->datestring() : "")
+#define LOGGER_DATE (Logger::getTheLog()->loggingdate() ? \
+                     Logger::getTheLog()->datestring() : "")
 
 #define LOGGER_DOLOG(L,X) LOGGER_PRT << LOGGER_DATE << ":" << L << ":" << \
                              __FILE__ << ":" << __LINE__ << "::" << X \
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/smallut.cpp 
new/libupnpp-0.21.0/libupnpp/smallut.cpp
--- old/libupnpp-0.20.2/libupnpp/smallut.cpp    2020-12-03 10:28:43.000000000 
+0100
+++ new/libupnpp-0.21.0/libupnpp/smallut.cpp    2021-02-11 10:01:13.000000000 
+0100
@@ -15,15 +15,6 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  *   02110-1301 USA
  */
-#ifdef _WIN32
-// needed for localtime_r under mingw. Has to exist before the other
-// includes from smallut.h
-#define _POSIX_THREAD_SAFE_FUNCTIONS
-#ifdef _MSC_VER
-#define localtime_r(a,b) localtime_s(b,a)
-#endif /* _MSC_VER */
-#endif /* _WIN32 */
-
 #include "smallut.h"
 
 #include <algorithm>
@@ -34,6 +25,7 @@
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
+#include <ctime>
 #include <iostream>
 #include <list>
 #include <numeric>
@@ -41,7 +33,13 @@
 #include <string>
 #include <unordered_map>
 #include <unordered_set>
-#include <ctime>
+
+#ifdef _WIN32
+#define strncasecmp _strnicmp
+#define strcasecmp _stricmp
+#define localtime_r(a,b) localtime_s(b,a)
+#endif /* _WIN32 */
+
 
 // Older compilers don't support stdc++ regex, but Windows does not
 // have the Linux one. Have a simple class to solve the simple cases.
@@ -262,20 +260,18 @@
 
 template <class T> void stringsToString(const T& tokens, string& s)
 {
-    for (auto it = tokens.begin();
-         it != tokens.end(); it++) {
-        bool hasblanks = false;
-        if (it->find_first_of(" \t\n") != string::npos) {
-            hasblanks = true;
-        }
-        if (it != tokens.begin()) {
-            s.append(1, ' ');
+    if (tokens.empty())
+        return;
+    for (const auto& tok : tokens) {
+        if (tok.empty()) {
+            s.append("\"\" ");
+            continue;
         }
+        bool hasblanks = tok.find_first_of(" \t\n") != string::npos;
         if (hasblanks) {
             s.append(1, '"');
         }
-        for (unsigned int i = 0; i < it->length(); i++) {
-            char car = it->at(i);
+        for (auto car : tok) {
             if (car == '"') {
                 s.append(1, '\\');
                 s.append(1, car);
@@ -286,7 +282,9 @@
         if (hasblanks) {
             s.append(1, '"');
         }
+        s.append(1, ' ');
     }
+    s.resize(s.size()-1);
 }
 
 template <class T> string stringsToString(const T& tokens)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/smallut.h 
new/libupnpp-0.21.0/libupnpp/smallut.h
--- old/libupnpp-0.20.2/libupnpp/smallut.h      2020-11-10 14:11:27.000000000 
+0100
+++ new/libupnpp-0.21.0/libupnpp/smallut.h      2021-02-09 16:01:16.000000000 
+0100
@@ -43,6 +43,13 @@
 #ifndef PRETEND_USE
 #define PRETEND_USE(var) ((void)(var))
 #endif
+#ifndef VERSION_AT_LEAST
+#define VERSION_AT_LEAST(LIBMAJ,LIBMIN,LIBREV,TARGMAJ,TARGMIN,TARGREV)  \
+    ((LIBMAJ) > (TARGMAJ) ||                                            \
+     ((LIBMAJ) == (TARGMAJ) &&                                          \
+      ((LIBMIN) > (TARGMIN) ||                                          \
+       ((LIBMIN) == (TARGMIN) && (LIBREV) >= (TARGREV)))))
+#endif
 #endif /* SMALLUT_DISABLE_MACROS */
 
 // Case-insensitive compare. ASCII ONLY !
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/upnpp_p.hxx 
new/libupnpp-0.21.0/libupnpp/upnpp_p.hxx
--- old/libupnpp-0.20.2/libupnpp/upnpp_p.hxx    2020-07-28 13:25:22.000000000 
+0200
+++ new/libupnpp-0.21.0/libupnpp/upnpp_p.hxx    2021-01-02 16:18:37.000000000 
+0100
@@ -75,6 +75,8 @@
 class LibUPnP::Internal {
 public:
 
+    int getSubsTimeout();
+    
     /** Specify function to be called on given UPnP
      *  event. The call will happen in the libupnp thread context.
      */
@@ -92,7 +94,6 @@
     };
 
     int setupWebServer(const std::string& description, UpnpDevice_Handle *dvh);
-
     UpnpClient_Handle getclh();
     
     bool ok;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/upnpplib.cxx 
new/libupnpp-0.21.0/libupnpp/upnpplib.cxx
--- old/libupnpp-0.20.2/libupnpp/upnpplib.cxx   2020-11-14 10:23:04.000000000 
+0100
+++ new/libupnpp-0.21.0/libupnpp/upnpplib.cxx   2021-03-01 15:17:38.000000000 
+0100
@@ -62,7 +62,9 @@
     std::string ifnames;
     std::string ipv4;
     int port;
-    
+    int substimeout{1800};
+    std::string clientproduct;
+    std::string clientversion;
 };
 static UPnPOptions options;
 
@@ -92,6 +94,15 @@
         case UPNPPINIT_OPTION_PORT:
             options.port = va_arg(ap, int);
             break;
+        case UPNPPINIT_OPTION_SUBSCRIPTION_TIMEOUT:
+            options.substimeout = va_arg(ap, int);
+            break;
+        case UPNPPINIT_OPTION_CLIENT_PRODUCT:
+            options.clientproduct = *((std::string*)(va_arg(ap, 
std::string*)));
+            break;
+        case UPNPPINIT_OPTION_CLIENT_VERSION:
+            options.clientversion = *((std::string*)(va_arg(ap, 
std::string*)));
+            break;
         default:
             std::cerr << "LibUPnP::init: unknown option value " << option 
<<"\n";
         }
@@ -186,6 +197,11 @@
     return addr;
 }
 
+int LibUPnP::Internal::getSubsTimeout()
+{
+    return options.substimeout;
+}
+
 LibUPnP::LibUPnP()
 {
     bool serveronly = 0 != (options.flags&UPNPPINIT_FLAG_SERVERONLY);
@@ -224,9 +240,14 @@
     }
     setMaxContentLength(2000*1024);
 
+#ifdef UPNP_ENABLE_IPV6
     LOGINF("LibUPnP: Using IPV4 " << UpnpGetServerIpAddress() << " port " <<
            UpnpGetServerPort() << " IPV6 " << UpnpGetServerIp6Address() <<
            " port " << UpnpGetServerPort6() << endl);
+#else
+    LOGINF("LibUPnP: Using IPV4 " << UpnpGetServerIpAddress() << " port " <<
+           UpnpGetServerPort() << endl);
+#endif
 
     // Client initialization is simple, just do it. Defer device
     // initialization because it's more complicated.
@@ -236,6 +257,12 @@
         m->init_error = UpnpRegisterClient(o_callback, (void *)this, &m->clh);
 
         if (m->init_error == UPNP_E_SUCCESS) {
+#if NPUPNP_VERSION >= 40100
+            if 
(!options.clientproduct.empty()&&!options.clientversion.empty()) {
+                UpnpClientSetProduct(m->clh, options.clientproduct.c_str(),
+                                     options.clientversion.c_str());
+            }
+#endif
             m->ok = true;
         } else {
             LOGERR(errAsString("UpnpRegisterClient", m->init_error) << endl);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/upnpplib.hxx 
new/libupnpp-0.21.0/libupnpp/upnpplib.hxx
--- old/libupnpp-0.20.2/libupnpp/upnpplib.hxx   2020-12-30 15:06:13.000000000 
+0100
+++ new/libupnpp-0.21.0/libupnpp/upnpplib.hxx   2021-01-18 19:05:20.000000000 
+0100
@@ -25,8 +25,8 @@
 
 /** Version components. */
 #define LIBUPNPP_VERSION_MAJOR 0
-#define LIBUPNPP_VERSION_MINOR 20
-#define LIBUPNPP_VERSION_REVISION 2
+#define LIBUPNPP_VERSION_MINOR 21
+#define LIBUPNPP_VERSION_REVISION 0
 /// Got this from Xapian...
 #define LIBUPNPP_AT_LEAST(A,B,C)                                        \
     (LIBUPNPP_VERSION_MAJOR > (A) ||                                    \
@@ -59,8 +59,8 @@
 
     /** Retrieve the singleton LibUPnP object.
      *
-     * Using this call with arguments is deprecated. Call init() then
-     * getLibUPnP() without arguments instead.
+     * Using this call with arguments is deprecated. Call init()
+     * (creates the lib) then getLibUPnP() without arguments instead.
      *
      * This initializes libupnp, possibly setting an address and port, possibly
      * registering a client if serveronly is false.
@@ -84,32 +84,46 @@
                                const std::string ip = std::string(),
                                unsigned short port = 0);
 
+    /** Configuration flags for the initialisation call */
     enum InitFlags {
         UPNPPINIT_FLAG_NONE = 0,
+        /** Disable IPV6 support */
         UPNPPINIT_FLAG_NOIPV6 = 1,
+        /** Do not initialize the client side (we are a device) */
         UPNPPINIT_FLAG_SERVERONLY = 2,
     };
 
+    /** Options for the initialisation call. Each option argument may be 
+     * followed by specific parameters. */
     enum InitOption {
         /** Terminate the VARARGs list. */
         UPNPPINIT_OPTION_END = 0,
-        /** Names of the interfaces to use. Space-separated list. If not
-         * set, we will use the first interface. If set to '*', will use
-         * all. const std::string* follows. */
+        /** Names of the interfaces to use. A const std::string* follows. 
+         * This is a space-separated list. If not
+         * set, we will use the first interface. If set to '*', we will use
+         * all possible interfaces. */
         UPNPPINIT_OPTION_IFNAMES,
-        /** Use single IPV4 address. This is incompatible with OPTION_IFNAMES. 
-         * const std::string* address in dot notation follows. */
+        /** Use single IPV4 address. A const std::string* address in 
+         * dot notation follows. This is incompatible with OPTION_IFNAMES. */
         UPNPPINIT_OPTION_IPV4,
-        /** IP Port to use. The lower lib default is 49152. 
-         * int follows */
+        /** IP Port to use. An int parameter follows. The lower lib default 
+         * is 49152. */
         UPNPPINIT_OPTION_PORT,
+        /** Control: subscription timeout in seconds. An int param. follows. */
+        UPNPPINIT_OPTION_SUBSCRIPTION_TIMEOUT,
+        /** Control: product name to set in user-agent strings.
+         * A const std::string* follows. */
+        UPNPPINIT_OPTION_CLIENT_PRODUCT,
+        /** Control: product version to set in user-agent strings.
+         * A const std::string* follows. */
+        UPNPPINIT_OPTION_CLIENT_VERSION,
     };
 
     /** Initialize the library.
      *
      * On success you will then call getLibUPnP() to access the object 
instance.
-     * @param flags OR'd InitFlags.
-     * @param ... A list of InitOption and values, ended by 
UPNPPINIT_OPTION_END.
+     * @param flags A bitfield of @ref InitFlags values.
+     * @param ... A list of @ref InitOption and values, ended by 
UPNPPINIT_OPTION_END.
      * @return false for failure, true for success. 
      */
     static bool init(unsigned int flags, ...);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnpp-0.20.2/libupnpp/workqueue.h 
new/libupnpp-0.21.0/libupnpp/workqueue.h
--- old/libupnpp-0.20.2/libupnpp/workqueue.h    2020-11-26 09:26:06.000000000 
+0100
+++ new/libupnpp-0.21.0/libupnpp/workqueue.h    2021-01-01 22:36:00.000000000 
+0100
@@ -72,6 +72,16 @@
         }
     }
 
+    /** Task deleter
+     * If put() is called with the flush option, and the tasks allocate memory,
+     * you need to set this function, which will be called on each task popped 
+     * from the queue. Tasks which go through normally must be freed by the 
+     * worker function.
+     */
+    void setTaskFreeFunc(void (*func)(T&)) {
+        m_taskfreefunc = func;
+    }
+
     /** Start the worker threads.
      *
      * @param nworkers number of threads copies to start.
@@ -120,6 +130,10 @@
         }
         if (flushprevious) {
             while (!m_queue.empty()) {
+                if (m_taskfreefunc) {
+                    T& d = m_queue.front();
+                    m_taskfreefunc(d);
+                }
                 m_queue.pop();
             }
         }
@@ -325,6 +339,7 @@
 #endif
     };
     
+    void (*m_taskfreefunc)(T&){nullptr};
     // Configuration
     std::string m_name;
     size_t m_high;

Reply via email to