|
a/src/upmpdutils.cxx |
|
b/src/upmpdutils.cxx |
1 |
/* Copyright (C) 2014 J.F.Dockes
|
1 |
/* Copyright (C) 2014 J.F.Dockes
|
2 |
* This program is free software; you can redistribute it and/or modify
|
2 |
* This program is free software; you can redistribute it and/or modify
|
3 |
* it under the terms of the GNU General Public License as published by
|
3 |
* it under the terms of the GNU General Public License as published by
|
4 |
* the Free Software Foundation; either version 2 of the License, or
|
4 |
* the Free Software Foundation; either version 2 of the License, or
|
5 |
* (at your option) any later version.
|
5 |
* (at your option) any later version.
|
6 |
*
|
6 |
*
|
7 |
* This program is distributed in the hope that it will be useful,
|
7 |
* This program is distributed in the hope that it will be useful,
|
8 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
8 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
9 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
9 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
10 |
* GNU General Public License for more details.
|
10 |
* GNU General Public License for more details.
|
11 |
*
|
11 |
*
|
12 |
* You should have received a copy of the GNU General Public License
|
12 |
* You should have received a copy of the GNU General Public License
|
13 |
* along with this program; if not, write to the
|
13 |
* along with this program; if not, write to the
|
14 |
* Free Software Foundation, Inc.,
|
14 |
* Free Software Foundation, Inc.,
|
15 |
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
15 |
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
16 |
*/
|
16 |
*/
|
17 |
|
17 |
|
18 |
//
|
18 |
//
|
19 |
// This file has a number of mostly uninteresting and badly
|
19 |
// This file has a number of mostly uninteresting and badly
|
20 |
// implemented small utility functions. This is a bit ugly, but I am
|
20 |
// implemented small utility functions. This is a bit ugly, but I am
|
21 |
// not linking to Qt or glib just to get path-concatenating
|
21 |
// not linking to Qt or glib just to get path-concatenating
|
22 |
// functions...
|
22 |
// functions...
|
23 |
|
23 |
|
|
... |
|
... |
52 |
|
52 |
|
53 |
using namespace std;
|
53 |
using namespace std;
|
54 |
using namespace UPnPP;
|
54 |
using namespace UPnPP;
|
55 |
using namespace UPnPClient;
|
55 |
using namespace UPnPClient;
|
56 |
|
56 |
|
57 |
// Append system error string to input string
|
|
|
58 |
void catstrerror(string *reason, const char *what, int _errno)
|
|
|
59 |
{
|
|
|
60 |
if (!reason)
|
|
|
61 |
return;
|
|
|
62 |
if (what)
|
|
|
63 |
reason->append(what);
|
|
|
64 |
|
|
|
65 |
reason->append(": errno: ");
|
|
|
66 |
|
|
|
67 |
char nbuf[20];
|
|
|
68 |
sprintf(nbuf, "%d", _errno);
|
|
|
69 |
reason->append(nbuf);
|
|
|
70 |
|
|
|
71 |
reason->append(" : ");
|
|
|
72 |
|
|
|
73 |
#ifdef sun
|
|
|
74 |
// Note: sun strerror is noted mt-safe ??
|
|
|
75 |
reason->append(strerror(_errno));
|
|
|
76 |
#else
|
|
|
77 |
#define ERRBUFSZ 200
|
|
|
78 |
char errbuf[ERRBUFSZ];
|
|
|
79 |
// There are 2 versions of strerror_r.
|
|
|
80 |
// - The GNU one returns a pointer to the message (maybe
|
|
|
81 |
// static storage or supplied buffer).
|
|
|
82 |
// - The POSIX one always stores in supplied buffer and
|
|
|
83 |
// returns 0 on success. As the possibility of error and
|
|
|
84 |
// error code are not specified, we're basically doomed
|
|
|
85 |
// cause we can't use a test on the 0 value to know if we
|
|
|
86 |
// were returned a pointer...
|
|
|
87 |
// Also couldn't find an easy way to disable the gnu version without
|
|
|
88 |
// changing the cxxflags globally, so forget it. Recent gnu lib versions
|
|
|
89 |
// normally default to the posix version.
|
|
|
90 |
// At worse we get no message at all here.
|
|
|
91 |
errbuf[0] = 0;
|
|
|
92 |
(void)strerror_r(_errno, errbuf, ERRBUFSZ);
|
|
|
93 |
reason->append(errbuf);
|
|
|
94 |
#endif
|
|
|
95 |
}
|
|
|
96 |
|
|
|
97 |
bool file_to_string(const string &fn, string &data, string *reason)
|
|
|
98 |
{
|
|
|
99 |
const int RDBUFSZ = 4096;
|
|
|
100 |
bool ret = false;
|
|
|
101 |
int fd = -1;
|
|
|
102 |
struct stat st;
|
|
|
103 |
|
|
|
104 |
fd = open(fn.c_str(), O_RDONLY|O_STREAMING);
|
|
|
105 |
if (fd < 0 || fstat(fd, &st) < 0) {
|
|
|
106 |
catstrerror(reason, "open/stat", errno);
|
|
|
107 |
return false;
|
|
|
108 |
}
|
|
|
109 |
|
|
|
110 |
data.reserve(st.st_size+1);
|
|
|
111 |
|
|
|
112 |
char buf[RDBUFSZ];
|
|
|
113 |
for (;;) {
|
|
|
114 |
int n = read(fd, buf, RDBUFSZ);
|
|
|
115 |
if (n < 0) {
|
|
|
116 |
catstrerror(reason, "read", errno);
|
|
|
117 |
goto out;
|
|
|
118 |
}
|
|
|
119 |
if (n == 0)
|
|
|
120 |
break;
|
|
|
121 |
|
|
|
122 |
data.append(buf, n);
|
|
|
123 |
}
|
|
|
124 |
|
|
|
125 |
ret = true;
|
|
|
126 |
out:
|
|
|
127 |
if (fd >= 0)
|
|
|
128 |
close(fd);
|
|
|
129 |
return ret;
|
|
|
130 |
}
|
|
|
131 |
|
|
|
132 |
void path_catslash(string &s) {
|
|
|
133 |
if (s.empty() || s[s.length() - 1] != '/')
|
|
|
134 |
s += '/';
|
|
|
135 |
}
|
|
|
136 |
|
|
|
137 |
string path_cat(const string &s1, const string &s2) {
|
|
|
138 |
string res = s1;
|
|
|
139 |
path_catslash(res);
|
|
|
140 |
res += s2;
|
|
|
141 |
return res;
|
|
|
142 |
}
|
|
|
143 |
|
|
|
144 |
bool path_exists(const string& path)
|
|
|
145 |
{
|
|
|
146 |
return access(path.c_str(), 0) == 0;
|
|
|
147 |
}
|
|
|
148 |
|
|
|
149 |
bool path_isabsolute(const string &path)
|
|
|
150 |
{
|
|
|
151 |
if (!path.empty() && (path[0] == '/'
|
|
|
152 |
#ifdef _WIN32
|
|
|
153 |
|| path_isdriveabs(path)
|
|
|
154 |
#endif
|
|
|
155 |
)) {
|
|
|
156 |
return true;
|
|
|
157 |
}
|
|
|
158 |
return false;
|
|
|
159 |
}
|
|
|
160 |
|
|
|
161 |
string path_home()
|
|
|
162 |
{
|
|
|
163 |
uid_t uid = getuid();
|
|
|
164 |
|
|
|
165 |
struct passwd *entry = getpwuid(uid);
|
|
|
166 |
if (entry == 0) {
|
|
|
167 |
const char *cp = getenv("HOME");
|
|
|
168 |
if (cp)
|
|
|
169 |
return cp;
|
|
|
170 |
else
|
|
|
171 |
return "/";
|
|
|
172 |
}
|
|
|
173 |
|
|
|
174 |
string homedir = entry->pw_dir;
|
|
|
175 |
path_catslash(homedir);
|
|
|
176 |
return homedir;
|
|
|
177 |
}
|
|
|
178 |
|
|
|
179 |
string path_tildexpand(const string &s)
|
|
|
180 |
{
|
|
|
181 |
if (s.empty() || s[0] != '~')
|
|
|
182 |
return s;
|
|
|
183 |
string o = s;
|
|
|
184 |
if (s.length() == 1) {
|
|
|
185 |
o.replace(0, 1, path_home());
|
|
|
186 |
} else if (s[1] == '/') {
|
|
|
187 |
o.replace(0, 2, path_home());
|
|
|
188 |
} else {
|
|
|
189 |
string::size_type pos = s.find('/');
|
|
|
190 |
int l = (pos == string::npos) ? s.length() - 1 : pos - 1;
|
|
|
191 |
struct passwd *entry = getpwnam(s.substr(1, l).c_str());
|
|
|
192 |
if (entry)
|
|
|
193 |
o.replace(0, l+1, entry->pw_dir);
|
|
|
194 |
}
|
|
|
195 |
return o;
|
|
|
196 |
}
|
|
|
197 |
|
|
|
198 |
bool path_makepath(const string& path, int mode)
|
|
|
199 |
{
|
|
|
200 |
if (path.empty() || path[0] != '/') {
|
|
|
201 |
return false;
|
|
|
202 |
}
|
|
|
203 |
vector<string> vpath;
|
|
|
204 |
stringToTokens(path, vpath, "/", true);
|
|
|
205 |
string npath;
|
|
|
206 |
for (auto it = vpath.begin(); it != vpath.end(); it++) {
|
|
|
207 |
npath += string("/") + *it;
|
|
|
208 |
if (access(npath.c_str(), 0) < 0) {
|
|
|
209 |
if (mkdir(npath.c_str(), mode)) {
|
|
|
210 |
return false;
|
|
|
211 |
}
|
|
|
212 |
}
|
|
|
213 |
}
|
|
|
214 |
return true;
|
|
|
215 |
}
|
|
|
216 |
|
|
|
217 |
void trimstring(string &s, const char *ws)
|
|
|
218 |
{
|
|
|
219 |
string::size_type pos = s.find_first_not_of(ws);
|
|
|
220 |
if (pos == string::npos) {
|
|
|
221 |
s.clear();
|
|
|
222 |
return;
|
|
|
223 |
}
|
|
|
224 |
s.replace(0, pos, string());
|
|
|
225 |
|
|
|
226 |
pos = s.find_last_not_of(ws);
|
|
|
227 |
if (pos != string::npos && pos != s.length()-1)
|
|
|
228 |
s.replace(pos+1, string::npos, string());
|
|
|
229 |
}
|
|
|
230 |
|
|
|
231 |
void stringToTokens(const string& str, vector<string>& tokens,
|
|
|
232 |
const string& delims, bool skipinit)
|
|
|
233 |
{
|
|
|
234 |
string::size_type startPos = 0, pos;
|
|
|
235 |
|
|
|
236 |
// Skip initial delims, return empty if this eats all.
|
|
|
237 |
if (skipinit &&
|
|
|
238 |
(startPos = str.find_first_not_of(delims, 0)) == string::npos) {
|
|
|
239 |
return;
|
|
|
240 |
}
|
|
|
241 |
while (startPos < str.size()) {
|
|
|
242 |
// Find next delimiter or end of string (end of token)
|
|
|
243 |
pos = str.find_first_of(delims, startPos);
|
|
|
244 |
|
|
|
245 |
// Add token to the vector and adjust start
|
|
|
246 |
if (pos == string::npos) {
|
|
|
247 |
tokens.push_back(str.substr(startPos));
|
|
|
248 |
break;
|
|
|
249 |
} else if (pos == startPos) {
|
|
|
250 |
// Dont' push empty tokens after first
|
|
|
251 |
if (tokens.empty())
|
|
|
252 |
tokens.push_back(string());
|
|
|
253 |
startPos = ++pos;
|
|
|
254 |
} else {
|
|
|
255 |
tokens.push_back(str.substr(startPos, pos - startPos));
|
|
|
256 |
startPos = ++pos;
|
|
|
257 |
}
|
|
|
258 |
}
|
|
|
259 |
}
|
|
|
260 |
|
|
|
261 |
|
|
|
262 |
template <class T> bool stringToStrings(const string &s, T &tokens,
|
|
|
263 |
const string& addseps)
|
|
|
264 |
{
|
|
|
265 |
string current;
|
|
|
266 |
tokens.clear();
|
|
|
267 |
enum states {SPACE, TOKEN, INQUOTE, ESCAPE};
|
|
|
268 |
states state = SPACE;
|
|
|
269 |
for (unsigned int i = 0; i < s.length(); i++) {
|
|
|
270 |
switch (s[i]) {
|
|
|
271 |
case '"':
|
|
|
272 |
switch(state) {
|
|
|
273 |
case SPACE:
|
|
|
274 |
state=INQUOTE; continue;
|
|
|
275 |
case TOKEN:
|
|
|
276 |
current += '"';
|
|
|
277 |
continue;
|
|
|
278 |
case INQUOTE:
|
|
|
279 |
tokens.insert(tokens.end(), current);
|
|
|
280 |
current.clear();
|
|
|
281 |
state = SPACE;
|
|
|
282 |
continue;
|
|
|
283 |
case ESCAPE:
|
|
|
284 |
current += '"';
|
|
|
285 |
state = INQUOTE;
|
|
|
286 |
continue;
|
|
|
287 |
}
|
|
|
288 |
break;
|
|
|
289 |
case '\\':
|
|
|
290 |
switch(state) {
|
|
|
291 |
case SPACE:
|
|
|
292 |
case TOKEN:
|
|
|
293 |
current += '\\';
|
|
|
294 |
state=TOKEN;
|
|
|
295 |
continue;
|
|
|
296 |
case INQUOTE:
|
|
|
297 |
state = ESCAPE;
|
|
|
298 |
continue;
|
|
|
299 |
case ESCAPE:
|
|
|
300 |
current += '\\';
|
|
|
301 |
state = INQUOTE;
|
|
|
302 |
continue;
|
|
|
303 |
}
|
|
|
304 |
break;
|
|
|
305 |
|
|
|
306 |
case ' ':
|
|
|
307 |
case '\t':
|
|
|
308 |
case '\n':
|
|
|
309 |
case '\r':
|
|
|
310 |
switch(state) {
|
|
|
311 |
case SPACE:
|
|
|
312 |
continue;
|
|
|
313 |
case TOKEN:
|
|
|
314 |
tokens.insert(tokens.end(), current);
|
|
|
315 |
current.clear();
|
|
|
316 |
state = SPACE;
|
|
|
317 |
continue;
|
|
|
318 |
case INQUOTE:
|
|
|
319 |
case ESCAPE:
|
|
|
320 |
current += s[i];
|
|
|
321 |
continue;
|
|
|
322 |
}
|
|
|
323 |
break;
|
|
|
324 |
|
|
|
325 |
default:
|
|
|
326 |
if (!addseps.empty() && addseps.find(s[i]) != string::npos) {
|
|
|
327 |
switch(state) {
|
|
|
328 |
case ESCAPE:
|
|
|
329 |
state = INQUOTE;
|
|
|
330 |
break;
|
|
|
331 |
case INQUOTE:
|
|
|
332 |
break;
|
|
|
333 |
case SPACE:
|
|
|
334 |
tokens.insert(tokens.end(), string(1, s[i]));
|
|
|
335 |
continue;
|
|
|
336 |
case TOKEN:
|
|
|
337 |
tokens.insert(tokens.end(), current);
|
|
|
338 |
current.erase();
|
|
|
339 |
tokens.insert(tokens.end(), string(1, s[i]));
|
|
|
340 |
state = SPACE;
|
|
|
341 |
continue;
|
|
|
342 |
}
|
|
|
343 |
} else switch(state) {
|
|
|
344 |
case ESCAPE:
|
|
|
345 |
state = INQUOTE;
|
|
|
346 |
break;
|
|
|
347 |
case SPACE:
|
|
|
348 |
state = TOKEN;
|
|
|
349 |
break;
|
|
|
350 |
case TOKEN:
|
|
|
351 |
case INQUOTE:
|
|
|
352 |
break;
|
|
|
353 |
}
|
|
|
354 |
current += s[i];
|
|
|
355 |
}
|
|
|
356 |
}
|
|
|
357 |
switch(state) {
|
|
|
358 |
case SPACE:
|
|
|
359 |
break;
|
|
|
360 |
case TOKEN:
|
|
|
361 |
tokens.insert(tokens.end(), current);
|
|
|
362 |
break;
|
|
|
363 |
case INQUOTE:
|
|
|
364 |
case ESCAPE:
|
|
|
365 |
return false;
|
|
|
366 |
}
|
|
|
367 |
return true;
|
|
|
368 |
}
|
|
|
369 |
|
|
|
370 |
template bool stringToStrings<vector<string> >(const string &,
|
|
|
371 |
vector<string> &,const string&);
|
|
|
372 |
|
|
|
373 |
// Translate 0-100% MPD volume to UPnP VolumeDB: we do db upnp-encoded
|
57 |
// Translate 0-100% MPD volume to UPnP VolumeDB: we do db upnp-encoded
|
374 |
// values from -10240 (0%) to 0 (100%)
|
58 |
// values from -10240 (0%) to 0 (100%)
|
375 |
int percentodbvalue(int value)
|
59 |
int percentodbvalue(int value)
|
376 |
{
|
60 |
{
|
377 |
int dbvalue;
|
61 |
int dbvalue;
|
378 |
if (value == 0) {
|
62 |
if (value == 0) {
|
379 |
dbvalue = -10240;
|
63 |
dbvalue = -10240;
|
380 |
} else {
|
64 |
} else {
|
381 |
float ratio = float(value)*value / 10000.0;
|
65 |
float ratio = float(value) * value / 10000.0;
|
382 |
float db = 10 * log10(ratio);
|
66 |
float db = 10 * log10(ratio);
|
383 |
dbvalue = int(256 * db);
|
67 |
dbvalue = int(256 * db);
|
384 |
}
|
68 |
}
|
385 |
return dbvalue;
|
69 |
return dbvalue;
|
386 |
}
|
70 |
}
|
|
... |
|
... |
395 |
|
79 |
|
396 |
// Translate VolumeDB to MPD 0-100
|
80 |
// Translate VolumeDB to MPD 0-100
|
397 |
int dbvaluetopercent(int dbvalue)
|
81 |
int dbvaluetopercent(int dbvalue)
|
398 |
{
|
82 |
{
|
399 |
float db = float(dbvalue) / 256.0;
|
83 |
float db = float(dbvalue) / 256.0;
|
400 |
float vol = exp10(db/10);
|
84 |
float vol = exp10(db / 10);
|
401 |
int percent = floor(sqrt(vol * 10000.0));
|
85 |
int percent = floor(sqrt(vol * 10000.0));
|
402 |
if (percent < 0) percent = 0;
|
86 |
if (percent < 0) {
|
403 |
if (percent > 100) percent = 100;
|
87 |
percent = 0;
|
|
|
88 |
}
|
|
|
89 |
if (percent > 100) {
|
|
|
90 |
percent = 100;
|
|
|
91 |
}
|
404 |
return percent;
|
92 |
return percent;
|
405 |
}
|
93 |
}
|
406 |
|
94 |
|
407 |
// Get from ssl unordered_map, return empty string for non-existing
|
95 |
// Get from ssl unordered_map, return empty string for non-existing
|
408 |
// key (so this only works for data where this behaviour makes sense).
|
96 |
// key (so this only works for data where this behaviour makes sense).
|
409 |
const string& mapget(const unordered_map<string, string>& im, const string& k)
|
97 |
const string& mapget(const unordered_map<string, string>& im, const string& k)
|
410 |
{
|
98 |
{
|
411 |
static string ns; // null string
|
99 |
static string ns; // null string
|
412 |
unordered_map<string, string>::const_iterator it = im.find(k);
|
100 |
unordered_map<string, string>::const_iterator it = im.find(k);
|
413 |
if (it == im.end())
|
101 |
if (it == im.end()) {
|
414 |
return ns;
|
102 |
return ns;
|
415 |
else
|
103 |
} else {
|
416 |
return it->second;
|
104 |
return it->second;
|
|
|
105 |
}
|
417 |
}
|
106 |
}
|
418 |
|
107 |
|
419 |
unordered_map<string, string>
|
108 |
unordered_map<string, string>
|
420 |
diffmaps(const unordered_map<string, string>& old,
|
109 |
diffmaps(const unordered_map<string, string>& old,
|
421 |
const unordered_map<string, string>& newer)
|
110 |
const unordered_map<string, string>& newer)
|
422 |
{
|
111 |
{
|
423 |
unordered_map<string, string> out;
|
112 |
unordered_map<string, string> out;
|
424 |
|
113 |
|
425 |
for (unordered_map<string, string>::const_iterator it = newer.begin();
|
114 |
for (unordered_map<string, string>::const_iterator it = newer.begin();
|
426 |
it != newer.end(); it++) {
|
115 |
it != newer.end(); it++) {
|
427 |
unordered_map<string, string>::const_iterator ito = old.find(it->first);
|
116 |
unordered_map<string, string>::const_iterator ito = old.find(it->first);
|
428 |
if (ito == old.end() || ito->second.compare(it->second))
|
117 |
if (ito == old.end() || ito->second.compare(it->second)) {
|
429 |
out[it->first] = it->second;
|
118 |
out[it->first] = it->second;
|
|
|
119 |
}
|
430 |
}
|
120 |
}
|
431 |
return out;
|
121 |
return out;
|
432 |
}
|
122 |
}
|
433 |
|
123 |
|
434 |
// Bogus didl fragment maker. We probably don't need a full-blown XML
|
124 |
// Bogus didl fragment maker. We probably don't need a full-blown XML
|
435 |
// helper here
|
125 |
// helper here
|
436 |
string didlmake(const UpSong& song)
|
126 |
string didlmake(const UpSong& song)
|
437 |
{
|
127 |
{
|
438 |
ostringstream ss;
|
128 |
ostringstream ss;
|
439 |
ss << "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
129 |
ss << "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
440 |
"<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
|
130 |
"<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
|
441 |
"xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" "
|
131 |
"xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" "
|
442 |
"xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" "
|
132 |
"xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" "
|
443 |
"xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\">"
|
133 |
"xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\">"
|
444 |
<< "<item restricted=\"1\">";
|
134 |
<< "<item restricted=\"1\">";
|
445 |
ss << "<orig>mpd</orig>";
|
135 |
ss << "<orig>mpd</orig>";
|
|
|
136 |
{
|
446 |
{ const string& val = song.title;
|
137 |
const string& val = song.title;
|
447 |
ss << "<dc:title>" << SoapHelp::xmlQuote(val) << "</dc:title>";
|
138 |
ss << "<dc:title>" << SoapHelp::xmlQuote(val) << "</dc:title>";
|
448 |
}
|
139 |
}
|
449 |
|
140 |
|
450 |
// TBD Playlists etc?
|
141 |
// TBD Playlists etc?
|
451 |
ss << "<upnp:class>object.item.audioItem.musicTrack</upnp:class>";
|
142 |
ss << "<upnp:class>object.item.audioItem.musicTrack</upnp:class>";
|
452 |
|
143 |
|
|
|
144 |
{
|
453 |
{ const string& val = song.artist;
|
145 |
const string& val = song.artist;
|
454 |
if (!val.empty()) {
|
146 |
if (!val.empty()) {
|
455 |
string a = SoapHelp::xmlQuote(val);
|
147 |
string a = SoapHelp::xmlQuote(val);
|
456 |
ss << "<dc:creator>" << a << "</dc:creator>" <<
|
148 |
ss << "<dc:creator>" << a << "</dc:creator>" <<
|
457 |
"<upnp:artist>" << a << "</upnp:artist>";
|
149 |
"<upnp:artist>" << a << "</upnp:artist>";
|
|
|
150 |
}
|
458 |
}
|
151 |
}
|
|
|
152 |
|
459 |
}
|
153 |
{
|
460 |
|
|
|
461 |
{ const string& val = song.album;
|
154 |
const string& val = song.album;
|
462 |
if (!val.empty()) {
|
155 |
if (!val.empty()) {
|
463 |
ss << "<upnp:album>" << SoapHelp::xmlQuote(val) << "</upnp:album>";
|
156 |
ss << "<upnp:album>" << SoapHelp::xmlQuote(val) << "</upnp:album>";
|
464 |
}
|
157 |
}
|
465 |
}
|
158 |
}
|
466 |
|
159 |
|
|
|
160 |
{
|
467 |
{ const string& val = song.genre;
|
161 |
const string& val = song.genre;
|
468 |
if (!val.empty()) {
|
162 |
if (!val.empty()) {
|
469 |
ss << "<upnp:genre>" << SoapHelp::xmlQuote(val) << "</upnp:genre>";
|
163 |
ss << "<upnp:genre>" << SoapHelp::xmlQuote(val) << "</upnp:genre>";
|
470 |
}
|
164 |
}
|
471 |
}
|
165 |
}
|
472 |
|
166 |
|
|
|
167 |
{
|
473 |
{string val = song.tracknum;
|
168 |
string val = song.tracknum;
|
474 |
// MPD may return something like xx/yy
|
169 |
// MPD may return something like xx/yy
|
475 |
string::size_type spos = val.find("/");
|
170 |
string::size_type spos = val.find("/");
|
476 |
if (spos != string::npos)
|
171 |
if (spos != string::npos) {
|
477 |
val = val.substr(0, spos);
|
172 |
val = val.substr(0, spos);
|
|
|
173 |
}
|
478 |
if (!val.empty()) {
|
174 |
if (!val.empty()) {
|
479 |
ss << "<upnp:originalTrackNumber>" << val <<
|
175 |
ss << "<upnp:originalTrackNumber>" << val <<
|
480 |
"</upnp:originalTrackNumber>";
|
176 |
"</upnp:originalTrackNumber>";
|
|
|
177 |
}
|
481 |
}
|
178 |
}
|
|
|
179 |
|
482 |
}
|
180 |
{
|
483 |
|
|
|
484 |
{const string& val = song.artUri;
|
181 |
const string& val = song.artUri;
|
485 |
if (!val.empty()) {
|
182 |
if (!val.empty()) {
|
486 |
ss << "<upnp:albumArtURI>" << SoapHelp::xmlQuote(val) <<
|
183 |
ss << "<upnp:albumArtURI>" << SoapHelp::xmlQuote(val) <<
|
487 |
"</upnp:albumArtURI>";
|
184 |
"</upnp:albumArtURI>";
|
488 |
}
|
185 |
}
|
489 |
}
|
186 |
}
|
490 |
|
187 |
|
491 |
// TBD: the res element normally has size, sampleFrequency,
|
188 |
// TBD: the res element normally has size, sampleFrequency,
|
492 |
// nrAudioChannels and protocolInfo attributes, which are bogus
|
189 |
// nrAudioChannels and protocolInfo attributes, which are bogus
|
493 |
// for the moment. partly because MPD does not supply them. And
|
190 |
// for the moment. partly because MPD does not supply them. And
|
494 |
// mostly everything is bogus if next is set...
|
191 |
// mostly everything is bogus if next is set...
|
495 |
|
192 |
|
496 |
ss << "<res " << "duration=\"" << upnpduration(song.duration_secs * 1000)
|
193 |
ss << "<res " << "duration=\"" << upnpduration(song.duration_secs * 1000)
|
497 |
<< "\" "
|
194 |
<< "\" "
|
498 |
// Bitrate keeps changing for VBRs and forces events. Keeping
|
195 |
// Bitrate keeps changing for VBRs and forces events. Keeping
|
499 |
// it out for now.
|
196 |
// it out for now.
|
500 |
// << "bitrate=\"" << mpds.kbrate << "\" "
|
197 |
// << "bitrate=\"" << mpds.kbrate << "\" "
|
501 |
<< "sampleFrequency=\"44100\" audioChannels=\"2\" "
|
198 |
<< "sampleFrequency=\"44100\" audioChannels=\"2\" "
|
502 |
<< "protocolInfo=\"http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000\""
|
199 |
<< "protocolInfo=\"http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000\""
|
503 |
<< ">"
|
200 |
<< ">"
|
504 |
<< SoapHelp::xmlQuote(song.uri)
|
201 |
<< SoapHelp::xmlQuote(song.uri)
|
505 |
<< "</res>"
|
202 |
<< "</res>"
|
506 |
<< "</item></DIDL-Lite>";
|
203 |
<< "</item></DIDL-Lite>";
|
507 |
return ss.str();
|
204 |
return ss.str();
|
508 |
}
|
205 |
}
|
509 |
|
206 |
|
510 |
bool uMetaToUpSong(const string& metadata, UpSong *ups)
|
207 |
bool uMetaToUpSong(const string& metadata, UpSong *ups)
|
511 |
{
|
208 |
{
|
512 |
if (ups == 0)
|
209 |
if (ups == 0) {
|
513 |
return false;
|
210 |
return false;
|
|
|
211 |
}
|
514 |
|
212 |
|
515 |
UPnPDirContent dirc;
|
213 |
UPnPDirContent dirc;
|
516 |
if (!dirc.parse(metadata) || dirc.m_items.size() == 0) {
|
214 |
if (!dirc.parse(metadata) || dirc.m_items.size() == 0) {
|
517 |
return false;
|
215 |
return false;
|
518 |
}
|
216 |
}
|
519 |
UPnPDirObject& dobj = *dirc.m_items.begin();
|
217 |
UPnPDirObject& dobj = *dirc.m_items.begin();
|
520 |
|
218 |
|
521 |
ups->artist = dobj.f2s("upnp:artist", false);
|
219 |
ups->artist = dobj.f2s("upnp:artist", false);
|
522 |
ups->album = dobj.f2s("upnp:album", false);
|
220 |
ups->album = dobj.f2s("upnp:album", false);
|
523 |
ups->title = dobj.m_title;
|
221 |
ups->title = dobj.m_title;
|
524 |
string stmp = dobj.f2s("duration", true);
|
222 |
string stmp = dobj.f2s("duration", true);
|
525 |
if (!stmp.empty()) {
|
223 |
if (!stmp.empty()) {
|
526 |
ups->duration_secs = upnpdurationtos(stmp);
|
224 |
ups->duration_secs = upnpdurationtos(stmp);
|
527 |
} else {
|
225 |
} else {
|
528 |
ups->duration_secs = 0;
|
226 |
ups->duration_secs = 0;
|
529 |
}
|
227 |
}
|
|
... |
|
... |
538 |
string regsub1(const string& sexp, const string& input, const string& repl)
|
236 |
string regsub1(const string& sexp, const string& input, const string& repl)
|
539 |
{
|
237 |
{
|
540 |
regex_t expr;
|
238 |
regex_t expr;
|
541 |
int err;
|
239 |
int err;
|
542 |
const int ERRSIZE = 200;
|
240 |
const int ERRSIZE = 200;
|
543 |
char errbuf[ERRSIZE+1];
|
241 |
char errbuf[ERRSIZE + 1];
|
544 |
regmatch_t pmatch[10];
|
242 |
regmatch_t pmatch[10];
|
545 |
|
243 |
|
546 |
if ((err = regcomp(&expr, sexp.c_str(), REG_EXTENDED))) {
|
244 |
if ((err = regcomp(&expr, sexp.c_str(), REG_EXTENDED))) {
|
547 |
regerror(err, &expr, errbuf, ERRSIZE);
|
245 |
regerror(err, &expr, errbuf, ERRSIZE);
|
548 |
LOGERR("upmpd: regsub1: regcomp() failed: " << errbuf << endl);
|
246 |
LOGERR("upmpd: regsub1: regcomp() failed: " << errbuf << endl);
|
549 |
return string();
|
247 |
return string();
|
550 |
}
|
248 |
}
|
551 |
|
249 |
|
552 |
if ((err = regexec(&expr, input.c_str(), 10, pmatch, 0))) {
|
250 |
if ((err = regexec(&expr, input.c_str(), 10, pmatch, 0))) {
|
553 |
regerror(err, &expr, errbuf, ERRSIZE);
|
251 |
regerror(err, &expr, errbuf, ERRSIZE);
|
554 |
//LOGDEB("upmpd: regsub1: regexec(" << sexp << ") failed: "
|
252 |
//LOGDEB("upmpd: regsub1: regexec(" << sexp << ") failed: "
|
555 |
// << errbuf << endl);
|
253 |
// << errbuf << endl);
|
556 |
regfree(&expr);
|
254 |
regfree(&expr);
|
|
... |
|
... |
565 |
out += repl;
|
263 |
out += repl;
|
566 |
out += input.substr(pmatch[0].rm_eo);
|
264 |
out += input.substr(pmatch[0].rm_eo);
|
567 |
regfree(&expr);
|
265 |
regfree(&expr);
|
568 |
return out;
|
266 |
return out;
|
569 |
}
|
267 |
}
|
570 |
|
|
|
571 |
// We do not want to mess with the pidfile content in the destructor:
|
|
|
572 |
// the lock might still be in use in a child process. In fact as much
|
|
|
573 |
// as we'd like to reset the pid inside the file when we're done, it
|
|
|
574 |
// would be very difficult to do it right and it's probably best left
|
|
|
575 |
// alone.
|
|
|
576 |
Pidfile::~Pidfile()
|
|
|
577 |
{
|
|
|
578 |
if (m_fd >= 0)
|
|
|
579 |
::close(m_fd);
|
|
|
580 |
m_fd = -1;
|
|
|
581 |
}
|
|
|
582 |
|
|
|
583 |
pid_t Pidfile::read_pid()
|
|
|
584 |
{
|
|
|
585 |
int fd = ::open(m_path.c_str(), O_RDONLY);
|
|
|
586 |
if (fd == -1)
|
|
|
587 |
return (pid_t)-1;
|
|
|
588 |
|
|
|
589 |
char buf[16];
|
|
|
590 |
int i = read(fd, buf, sizeof(buf) - 1);
|
|
|
591 |
::close(fd);
|
|
|
592 |
if (i <= 0)
|
|
|
593 |
return (pid_t)-1;
|
|
|
594 |
buf[i] = '\0';
|
|
|
595 |
char *endptr;
|
|
|
596 |
pid_t pid = strtol(buf, &endptr, 10);
|
|
|
597 |
if (endptr != &buf[i])
|
|
|
598 |
return (pid_t)-1;
|
|
|
599 |
return pid;
|
|
|
600 |
}
|
|
|
601 |
|
|
|
602 |
int Pidfile::flopen()
|
|
|
603 |
{
|
|
|
604 |
const char *path = m_path.c_str();
|
|
|
605 |
if ((m_fd = ::open(path, O_RDWR|O_CREAT, 0644)) == -1) {
|
|
|
606 |
m_reason = "Open failed: [" + m_path + "]: " + strerror(errno);
|
|
|
607 |
return -1;
|
|
|
608 |
}
|
|
|
609 |
|
|
|
610 |
#ifdef sun
|
|
|
611 |
struct flock lockdata;
|
|
|
612 |
lockdata.l_start = 0;
|
|
|
613 |
lockdata.l_len = 0;
|
|
|
614 |
lockdata.l_type = F_WRLCK;
|
|
|
615 |
lockdata.l_whence = SEEK_SET;
|
|
|
616 |
if (fcntl(m_fd, F_SETLK, &lockdata) != 0) {
|
|
|
617 |
int serrno = errno;
|
|
|
618 |
(void)::close(m_fd);
|
|
|
619 |
errno = serrno;
|
|
|
620 |
m_reason = "fcntl lock failed";
|
|
|
621 |
return -1;
|
|
|
622 |
}
|
|
|
623 |
#else
|
|
|
624 |
int operation = LOCK_EX | LOCK_NB;
|
|
|
625 |
if (flock(m_fd, operation) == -1) {
|
|
|
626 |
int serrno = errno;
|
|
|
627 |
(void)::close(m_fd);
|
|
|
628 |
errno = serrno;
|
|
|
629 |
m_reason = "flock failed";
|
|
|
630 |
return -1;
|
|
|
631 |
}
|
|
|
632 |
#endif // ! sun
|
|
|
633 |
|
|
|
634 |
if (ftruncate(m_fd, 0) != 0) {
|
|
|
635 |
/* can't happen [tm] */
|
|
|
636 |
int serrno = errno;
|
|
|
637 |
(void)::close(m_fd);
|
|
|
638 |
errno = serrno;
|
|
|
639 |
m_reason = "ftruncate failed";
|
|
|
640 |
return -1;
|
|
|
641 |
}
|
|
|
642 |
return 0;
|
|
|
643 |
}
|
|
|
644 |
|
|
|
645 |
pid_t Pidfile::open()
|
|
|
646 |
{
|
|
|
647 |
if (flopen() < 0) {
|
|
|
648 |
return read_pid();
|
|
|
649 |
}
|
|
|
650 |
return (pid_t)0;
|
|
|
651 |
}
|
|
|
652 |
|
|
|
653 |
int Pidfile::write_pid()
|
|
|
654 |
{
|
|
|
655 |
/* truncate to allow multiple calls */
|
|
|
656 |
if (ftruncate(m_fd, 0) == -1) {
|
|
|
657 |
m_reason = "ftruncate failed";
|
|
|
658 |
return -1;
|
|
|
659 |
}
|
|
|
660 |
char pidstr[20];
|
|
|
661 |
sprintf(pidstr, "%u", int(getpid()));
|
|
|
662 |
lseek(m_fd, 0, 0);
|
|
|
663 |
if (::write(m_fd, pidstr, strlen(pidstr)) != (ssize_t)strlen(pidstr)) {
|
|
|
664 |
m_reason = "write failed";
|
|
|
665 |
return -1;
|
|
|
666 |
}
|
|
|
667 |
return 0;
|
|
|
668 |
}
|
|
|
669 |
|
|
|
670 |
int Pidfile::close()
|
|
|
671 |
{
|
|
|
672 |
return ::close(m_fd);
|
|
|
673 |
}
|
|
|
674 |
|
|
|
675 |
int Pidfile::remove()
|
|
|
676 |
{
|
|
|
677 |
return unlink(m_path.c_str());
|
|
|
678 |
}
|
|
|