ContentDirectoryService.cxx 8.18 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 * http://www.musicpd.org
 *
 * 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.
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

20
#include "lib/upnp/ContentDirectoryService.hxx"
21 22 23 24
#include "config.h"
#ifdef USING_PUPNP
#	include "lib/upnp/ixmlwrap.hxx"
#endif
25
#include "lib/upnp/UniqueIxml.hxx"
26
#include "lib/upnp/Action.hxx"
27
#include "Directory.hxx"
28
#include "util/NumberParser.hxx"
29
#include "util/RuntimeError.hxx"
30
#include "util/ScopeExit.hxx"
31
#include "util/StringFormat.hxx"
32

33 34 35
#include <algorithm>

#ifdef USING_PUPNP
36 37
static void
ReadResultTag(UPnPDirContent &dirbuf, IXML_Document *response)
38 39 40 41 42
{
	const char *p = ixmlwrap::getFirstElementValue(response, "Result");
	if (p == nullptr)
		p = "";

43
	dirbuf.Parse(p);
44
}
45
#endif
46

47
inline void
48
ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
49 50
				      const char *objectId, unsigned offset,
				      unsigned count, UPnPDirContent &dirbuf,
51 52
				      unsigned &didreadp,
				      unsigned &totalp) const
53
{
54
#ifdef USING_PUPNP
55
	// Some devices require an empty SortCriteria, else bad params
56
	IXML_Document *request =
57 58 59 60 61
		MakeActionHelper("Browse", m_serviceType.c_str(),
				 "ObjectID", objectId,
				 "BrowseFlag", "BrowseDirectChildren",
				 "Filter", "*",
				 "SortCriteria", "",
62 63 64 65
				 "StartingIndex",
				 StringFormat<32>("%u", offset).c_str(),
				 "RequestedCount",
				 StringFormat<32>("%u", count).c_str());
66 67
	if (request == nullptr)
		throw std::runtime_error("UpnpMakeAction() failed");
68

69 70
	AtScopeExit(request) { ixmlDocument_free(request); };

71
	IXML_Document *response;
72
	int code = UpnpSendAction(hdl, m_actionURL.c_str(), m_serviceType.c_str(),
73
				  nullptr /*devUDN*/, request, &response);
74 75 76
	if (code != UPNP_E_SUCCESS)
		throw FormatRuntimeError("UpnpSendAction() failed: %s",
					 UpnpGetErrorMessage(code));
77

78 79
	AtScopeExit(response) { ixmlDocument_free(response); };

80
	const char *value = ixmlwrap::getFirstElementValue(response, "NumberReturned");
81 82 83
	didreadp = value != nullptr
		? ParseUnsigned(value)
		: 0;
84

85 86
	value = ixmlwrap::getFirstElementValue(response, "TotalMatches");
	if (value != nullptr)
87
		totalp = ParseUnsigned(value);
88

89
	ReadResultTag(dirbuf, response);
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
#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
119 120
}

121
UPnPDirContent
122
ContentDirectoryService::readDir(UpnpClient_Handle handle,
123
				 const char *objectId) const
124
{
125
	UPnPDirContent dirbuf;
126
	unsigned offset = 0, total = -1, count;
127

128
	do {
129 130
		readDirSlice(handle, objectId, offset, m_rdreqcnt, dirbuf,
			     count, total);
131 132

		offset += count;
133
	} while (count > 0 && offset < total);
134

135
	return dirbuf;
136 137
}

138
UPnPDirContent
139 140
ContentDirectoryService::search(UpnpClient_Handle hdl,
				const char *objectId,
141
				const char *ss) const
142
{
143
	UPnPDirContent dirbuf;
144
	unsigned offset = 0, total = -1, count;
145

146
	do {
147
#ifdef USING_PUPNP
148 149 150 151 152
		UniqueIxmlDocument request(MakeActionHelper("Search", m_serviceType.c_str(),
							    "ContainerID", objectId,
							    "SearchCriteria", ss,
							    "Filter", "*",
							    "SortCriteria", "",
153 154
							    "StartingIndex",
							    StringFormat<32>("%u", offset).c_str(),
155 156
							    "RequestedCount", "0")); // Setting a value here gets twonky into fits
		if (!request)
157
			throw std::runtime_error("UpnpMakeAction() failed");
158

159
		IXML_Document *_response;
160 161
		auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
					   m_serviceType.c_str(),
162
					   nullptr /*devUDN*/,
163
					   request.get(), &_response);
164 165 166
		if (code != UPNP_E_SUCCESS)
			throw FormatRuntimeError("UpnpSendAction() failed: %s",
						 UpnpGetErrorMessage(code));
167

168 169
		UniqueIxmlDocument response(_response);

170
		const char *value =
171 172
			ixmlwrap::getFirstElementValue(response.get(),
						       "NumberReturned");
173
		count = value != nullptr
174 175
			? ParseUnsigned(value)
			: 0;
176 177 178

		offset += count;

179 180
		value = ixmlwrap::getFirstElementValue(response.get(),
						       "TotalMatches");
181
		if (value != nullptr)
182
			total = ParseUnsigned(value);
183

184
		ReadResultTag(dirbuf, response.get());
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
#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
215
	} while (count > 0 && offset < total);
216

217
	return dirbuf;
218 219
}

220
UPnPDirContent
221
ContentDirectoryService::getMetadata(UpnpClient_Handle hdl,
222
				     const char *objectId) const
223
{
224
#ifdef USING_PUPNP
225
	// Create request
226 227 228 229 230 231 232
	UniqueIxmlDocument request(MakeActionHelper("Browse", m_serviceType.c_str(),
						    "ObjectID", objectId,
						    "BrowseFlag", "BrowseMetadata",
						    "Filter", "*",
						    "SortCriteria", "",
						    "StartingIndex", "0",
						    "RequestedCount", "1"));
233 234
	if (request == nullptr)
		throw std::runtime_error("UpnpMakeAction() failed");
235

236
	IXML_Document *_response;
237 238
	auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
				   m_serviceType.c_str(),
239
				   nullptr /*devUDN*/, request.get(), &_response);
240 241 242
	if (code != UPNP_E_SUCCESS)
		throw FormatRuntimeError("UpnpSendAction() failed: %s",
					 UpnpGetErrorMessage(code));
243

244
	UniqueIxmlDocument response(_response);
245 246 247
	UPnPDirContent dirbuf;
	ReadResultTag(dirbuf, response.get());
	return dirbuf;
248 249 250 251 252 253 254 255 256 257 258 259 260
#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));
261 262
	auto it = std::find_if(responseData.begin(), responseData.end(), [](auto&& entry){ return entry.first == "Result"; });
	const char *p = it != responseData.end() ? it->second.c_str() : "";
263 264 265 266
	UPnPDirContent dirbuf;
	dirbuf.Parse(p);
	return dirbuf;
#endif
267
}