Merge branch 'npu' of git://github.com/neheb/MPD

This commit is contained in:
Max Kellermann 2021-05-19 09:47:44 +02:00
commit d747576793
10 changed files with 167 additions and 13 deletions

1
NEWS
View File

@ -6,6 +6,7 @@ ver 0.23 (not yet released)
- proxy: require MPD 0.20 or later - proxy: require MPD 0.20 or later
- proxy: require libmpdclient 2.11 or later - proxy: require libmpdclient 2.11 or later
- proxy: split search into chunks to avoid exceeding the output buffer - proxy: split search into chunks to avoid exceeding the output buffer
- upnp: support libnpupnp instead of libupnp
* output * output
- pipewire: new plugin - pipewire: new plugin
- snapcast: new plugin - snapcast: new plugin

View File

@ -62,7 +62,10 @@ option('dsd', type: 'boolean', value: true, description: 'Support the DSD audio
# #
option('database', type: 'boolean', value: true, description: 'enable support for the music database') option('database', type: 'boolean', value: true, description: 'enable support for the music database')
option('upnp', type: 'feature', description: 'UPnP client support') option('upnp', type: 'combo',
choices: ['auto', 'pupnp', 'npupnp', 'disabled'],
value: 'auto',
description: 'UPnP client support')
option('libmpdclient', type: 'feature', description: 'libmpdclient support (for the proxy database plugin)') option('libmpdclient', type: 'feature', description: 'libmpdclient support (for the proxy database plugin)')
# #

View File

@ -18,7 +18,10 @@
*/ */
#include "lib/upnp/ContentDirectoryService.hxx" #include "lib/upnp/ContentDirectoryService.hxx"
#include "config.h"
#ifdef USING_PUPNP
# include "lib/upnp/ixmlwrap.hxx" # include "lib/upnp/ixmlwrap.hxx"
#endif
#include "lib/upnp/UniqueIxml.hxx" #include "lib/upnp/UniqueIxml.hxx"
#include "lib/upnp/Action.hxx" #include "lib/upnp/Action.hxx"
#include "Directory.hxx" #include "Directory.hxx"
@ -27,6 +30,9 @@
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
#include "util/StringFormat.hxx" #include "util/StringFormat.hxx"
#include <algorithm>
#ifdef USING_PUPNP
static void static void
ReadResultTag(UPnPDirContent &dirbuf, IXML_Document *response) ReadResultTag(UPnPDirContent &dirbuf, IXML_Document *response)
{ {
@ -36,6 +42,7 @@ ReadResultTag(UPnPDirContent &dirbuf, IXML_Document *response)
dirbuf.Parse(p); dirbuf.Parse(p);
} }
#endif
inline void inline void
ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl, ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
@ -44,6 +51,7 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
unsigned &didreadp, unsigned &didreadp,
unsigned &totalp) const unsigned &totalp) const
{ {
#ifdef USING_PUPNP
// Some devices require an empty SortCriteria, else bad params // Some devices require an empty SortCriteria, else bad params
IXML_Document *request = IXML_Document *request =
MakeActionHelper("Browse", m_serviceType.c_str(), MakeActionHelper("Browse", m_serviceType.c_str(),
@ -79,6 +87,35 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
totalp = ParseUnsigned(value); totalp = ParseUnsigned(value);
ReadResultTag(dirbuf, response); ReadResultTag(dirbuf, response);
#else
std::vector<std::pair<std::string, std::string>> actionParams{
{"ObjectID", objectId},
{"BrowseFlag", "BrowseDirectChildren"},
{"Filter", "*"},
{"SortCriteria", ""},
{"StartingIndex", StringFormat<32>("%u", offset).c_str()},
{"RequestedCount", StringFormat<32>("%u", count).c_str()}};
std::vector<std::pair<std::string, std::string>> responseData;
int errcode;
std::string errdesc;
int code = UpnpSendAction(hdl, "", m_actionURL, m_serviceType, "Browse",
actionParams, responseData, &errcode, errdesc);
if (code != UPNP_E_SUCCESS)
throw FormatRuntimeError("UpnpSendAction() failed: %s",
UpnpGetErrorMessage(code));
const char *p = "";
didreadp = 0;
for (const auto &entry : responseData) {
if (entry.first == "Result") {
p = entry.second.c_str();
} else if (entry.first == "TotalMatches") {
totalp = ParseUnsigned(entry.second.c_str());
} else if (entry.first == "NumberReturned") {
didreadp = ParseUnsigned(entry.second.c_str());
}
}
dirbuf.Parse(p);
#endif
} }
UPnPDirContent UPnPDirContent
@ -107,6 +144,7 @@ ContentDirectoryService::search(UpnpClient_Handle hdl,
unsigned offset = 0, total = -1, count; unsigned offset = 0, total = -1, count;
do { do {
#ifdef USING_PUPNP
UniqueIxmlDocument request(MakeActionHelper("Search", m_serviceType.c_str(), UniqueIxmlDocument request(MakeActionHelper("Search", m_serviceType.c_str(),
"ContainerID", objectId, "ContainerID", objectId,
"SearchCriteria", ss, "SearchCriteria", ss,
@ -144,6 +182,36 @@ ContentDirectoryService::search(UpnpClient_Handle hdl,
total = ParseUnsigned(value); total = ParseUnsigned(value);
ReadResultTag(dirbuf, response.get()); ReadResultTag(dirbuf, response.get());
#else
std::vector<std::pair<std::string, std::string>> actionParams{
{"ContainerID", objectId},
{"SearchCriteria", ss},
{"Filter", "*"},
{"SortCriteria", ""},
{"StartingIndex", StringFormat<32>("%u", offset).c_str()},
{"RequestedCount", "0"}};
std::vector<std::pair<std::string, std::string>> responseData;
int errcode;
std::string errdesc;
int code = UpnpSendAction(hdl, "", m_actionURL, m_serviceType, "Search",
actionParams, responseData, &errcode, errdesc);
if (code != UPNP_E_SUCCESS)
throw FormatRuntimeError("UpnpSendAction() failed: %s",
UpnpGetErrorMessage(code));
const char *p = "";
count = 0;
for (const auto &entry : responseData) {
if (entry.first == "Result") {
p = entry.second.c_str();
} else if (entry.first == "TotalMatches") {
total = ParseUnsigned(entry.second.c_str());
} else if (entry.first == "NumberReturned") {
count = ParseUnsigned(entry.second.c_str());
offset += count;
}
}
dirbuf.Parse(p);
#endif
} while (count > 0 && offset < total); } while (count > 0 && offset < total);
return dirbuf; return dirbuf;
@ -153,6 +221,7 @@ UPnPDirContent
ContentDirectoryService::getMetadata(UpnpClient_Handle hdl, ContentDirectoryService::getMetadata(UpnpClient_Handle hdl,
const char *objectId) const const char *objectId) const
{ {
#ifdef USING_PUPNP
// Create request // Create request
UniqueIxmlDocument request(MakeActionHelper("Browse", m_serviceType.c_str(), UniqueIxmlDocument request(MakeActionHelper("Browse", m_serviceType.c_str(),
"ObjectID", objectId, "ObjectID", objectId,
@ -176,4 +245,27 @@ ContentDirectoryService::getMetadata(UpnpClient_Handle hdl,
UPnPDirContent dirbuf; UPnPDirContent dirbuf;
ReadResultTag(dirbuf, response.get()); ReadResultTag(dirbuf, response.get());
return dirbuf; return dirbuf;
#else
std::vector<std::pair<std::string, std::string>> actionParams{
{"ObjectID", objectId}, {"BrowseFlag", "BrowseMetadata"},
{"Filter", "*"}, {"SortCriteria", ""},
{"StartingIndex", "0"}, {"RequestedCount", "1"}};
std::vector<std::pair<std::string, std::string>> responseData;
int errcode;
std::string errdesc;
int code = UpnpSendAction(hdl, "", m_actionURL, m_serviceType, "Browse",
actionParams, responseData, &errcode, errdesc);
if (code != UPNP_E_SUCCESS)
throw FormatRuntimeError("UpnpSendAction() failed: %s",
UpnpGetErrorMessage(code));
const char *p = "";
for (const auto &entry : responseData) {
if (entry.first == "Result") {
p = entry.second.c_str();
}
}
UPnPDirContent dirbuf;
dirbuf.Parse(p);
return dirbuf;
#endif
} }

View File

@ -38,6 +38,7 @@ CountNameValuePairs([[maybe_unused]] const char *name, [[maybe_unused]] const ch
return 1 + CountNameValuePairs(args...); return 1 + CountNameValuePairs(args...);
} }
#ifdef USING_PUPNP
/** /**
* A wrapper for UpnpMakeAction() that counts the number of name/value * A wrapper for UpnpMakeAction() that counts the number of name/value
* pairs and adds the nullptr sentinel. * pairs and adds the nullptr sentinel.
@ -52,5 +53,6 @@ MakeActionHelper(const char *action_name, const char *service_type,
args..., args...,
nullptr, nullptr); nullptr, nullptr);
} }
#endif
#endif #endif

View File

@ -17,13 +17,21 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "config.h"
#include "ContentDirectoryService.hxx" #include "ContentDirectoryService.hxx"
#include "UniqueIxml.hxx"
#include "Device.hxx" #include "Device.hxx"
#include "UniqueIxml.hxx"
#ifdef USING_PUPNP
# include "ixmlwrap.hxx" # include "ixmlwrap.hxx"
#include "util/UriRelative.hxx" #endif
#include "util/RuntimeError.hxx" #include "Action.hxx"
#include "util/IterableSplitString.hxx" #include "util/IterableSplitString.hxx"
#include "util/RuntimeError.hxx"
#include "util/UriRelative.hxx"
#include "util/UriUtil.hxx"
#include <algorithm>
#include <upnptools.h> #include <upnptools.h>
@ -50,6 +58,7 @@ ContentDirectoryService::~ContentDirectoryService() noexcept = default;
std::forward_list<std::string> std::forward_list<std::string>
ContentDirectoryService::getSearchCapabilities(UpnpClient_Handle hdl) const ContentDirectoryService::getSearchCapabilities(UpnpClient_Handle hdl) const
{ {
#ifdef USING_PUPNP
UniqueIxmlDocument request(UpnpMakeAction("GetSearchCapabilities", m_serviceType.c_str(), UniqueIxmlDocument request(UpnpMakeAction("GetSearchCapabilities", m_serviceType.c_str(),
0, 0,
nullptr, nullptr)); nullptr, nullptr));
@ -68,6 +77,23 @@ ContentDirectoryService::getSearchCapabilities(UpnpClient_Handle hdl) const
const char *s = ixmlwrap::getFirstElementValue(response.get(), const char *s = ixmlwrap::getFirstElementValue(response.get(),
"SearchCaps"); "SearchCaps");
#else
std::vector<std::pair<std::string, std::string>> responseData;
int errcode;
std::string errdesc;
auto code = UpnpSendAction(hdl, "", m_actionURL, m_serviceType,
"GetSearchCapabilities", {}, responseData, &errcode,
errdesc);
if (code != UPNP_E_SUCCESS)
throw FormatRuntimeError("UpnpSendAction() failed: %s",
UpnpGetErrorMessage(code));
const char *s{nullptr};
for (auto &entry : responseData) {
if (entry.first == "SearchCaps") {
s = entry.second.c_str();
}
}
#endif
if (s == nullptr || *s == 0) if (s == nullptr || *s == 0)
return {}; return {};

View File

@ -23,7 +23,9 @@
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include <upnptools.h> #include <upnptools.h>
#ifdef USING_PUPNP
# include <ixml.h> # include <ixml.h>
#endif
#include <cassert> #include <cassert>
@ -44,8 +46,10 @@ DoInit()
UpnpSetMaxContentLength(2000*1024); UpnpSetMaxContentLength(2000*1024);
#ifdef USING_PUPNP
// Servers sometimes make error (e.g.: minidlna returns bad utf-8) // Servers sometimes make error (e.g.: minidlna returns bad utf-8)
ixmlRelaxParser(1); ixmlRelaxParser(1);
#endif
} }
void void

View File

@ -20,6 +20,7 @@
#ifndef MPD_UPNP_UNIQUE_XML_HXX #ifndef MPD_UPNP_UNIQUE_XML_HXX
#define MPD_UPNP_UNIQUE_XML_HXX #define MPD_UPNP_UNIQUE_XML_HXX
#ifdef USING_PUPNP
# include <ixml.h> # include <ixml.h>
# include <memory> # include <memory>
@ -37,4 +38,5 @@ struct UpnpIxmlDeleter {
typedef std::unique_ptr<IXML_Document, UpnpIxmlDeleter> UniqueIxmlDocument; typedef std::unique_ptr<IXML_Document, UpnpIxmlDeleter> UniqueIxmlDocument;
typedef std::unique_ptr<IXML_NodeList, UpnpIxmlDeleter> UniqueIxmlNodeList; typedef std::unique_ptr<IXML_NodeList, UpnpIxmlDeleter> UniqueIxmlNodeList;
#endif /* USING_PUPNP */
#endif #endif

View File

@ -15,6 +15,9 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/ */
#include "config.h"
#ifdef USING_PUPNP
# include "ixmlwrap.hxx" # include "ixmlwrap.hxx"
# include "UniqueIxml.hxx" # include "UniqueIxml.hxx"
@ -39,3 +42,4 @@ getFirstElementValue(IXML_Document *doc, const char *name) noexcept
} }
} // namespace ixmlwrap } // namespace ixmlwrap
#endif

View File

@ -17,6 +17,7 @@
#ifndef _IXMLWRAP_H_INCLUDED_ #ifndef _IXMLWRAP_H_INCLUDED_
#define _IXMLWRAP_H_INCLUDED_ #define _IXMLWRAP_H_INCLUDED_
#ifdef USING_PUPNP
# include <ixml.h> # include <ixml.h>
namespace ixmlwrap { namespace ixmlwrap {
@ -30,4 +31,5 @@ namespace ixmlwrap {
} }
#endif /* USING_PUPNP */
#endif /* _IXMLWRAP_H_INCLUDED_ */ #endif /* _IXMLWRAP_H_INCLUDED_ */

View File

@ -1,4 +1,22 @@
upnp_dep = dependency('libupnp', version: '>= 1.8', required: get_option('upnp')) upnp_option = get_option('upnp')
if upnp_option == 'auto'
upnp_dep = dependency('libupnp', version: '>= 1.8', required: false)
conf.set('USING_PUPNP', upnp_dep.found())
if not upnp_dep.found()
upnp_dep = dependency('libnpupnp', version: '>= 1.8', required: false)
endif
elif upnp_option == 'pupnp'
upnp_dep = dependency('libupnp', version: '>= 1.8', required: true)
conf.set('USING_PUPNP', true)
elif upnp_option == 'npupnp'
upnp_dep = dependency('libnpupnp', required: true)
conf.set('USING_PUPNP', false)
elif upnp_option == 'disabled'
upnp_dep = dependency('', required: false)
subdir_done()
endif
conf.set('ENABLE_UPNP', upnp_dep.found()) conf.set('ENABLE_UPNP', upnp_dep.found())
if not upnp_dep.found() if not upnp_dep.found()
subdir_done() subdir_done()