--- a/src/upmpdutils.cxx
+++ b/src/upmpdutils.cxx
@@ -1,21 +1,21 @@
/* Copyright (C) 2014 J.F.Dockes
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the
- * Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-//
+//
// This file has a number of mostly uninteresting and badly
// implemented small utility functions. This is a bit ugly, but I am
// not linking to Qt or glib just to get path-concatenating
@@ -54,322 +54,6 @@
using namespace UPnPP;
using namespace UPnPClient;
-// Append system error string to input string
-void catstrerror(string *reason, const char *what, int _errno)
-{
- if (!reason)
- return;
- if (what)
- reason->append(what);
-
- reason->append(": errno: ");
-
- char nbuf[20];
- sprintf(nbuf, "%d", _errno);
- reason->append(nbuf);
-
- reason->append(" : ");
-
-#ifdef sun
- // Note: sun strerror is noted mt-safe ??
- reason->append(strerror(_errno));
-#else
-#define ERRBUFSZ 200
- char errbuf[ERRBUFSZ];
- // There are 2 versions of strerror_r.
- // - The GNU one returns a pointer to the message (maybe
- // static storage or supplied buffer).
- // - The POSIX one always stores in supplied buffer and
- // returns 0 on success. As the possibility of error and
- // error code are not specified, we're basically doomed
- // cause we can't use a test on the 0 value to know if we
- // were returned a pointer...
- // Also couldn't find an easy way to disable the gnu version without
- // changing the cxxflags globally, so forget it. Recent gnu lib versions
- // normally default to the posix version.
- // At worse we get no message at all here.
- errbuf[0] = 0;
- (void)strerror_r(_errno, errbuf, ERRBUFSZ);
- reason->append(errbuf);
-#endif
-}
-
-bool file_to_string(const string &fn, string &data, string *reason)
-{
- const int RDBUFSZ = 4096;
- bool ret = false;
- int fd = -1;
- struct stat st;
-
- fd = open(fn.c_str(), O_RDONLY|O_STREAMING);
- if (fd < 0 || fstat(fd, &st) < 0) {
- catstrerror(reason, "open/stat", errno);
- return false;
- }
-
- data.reserve(st.st_size+1);
-
- char buf[RDBUFSZ];
- for (;;) {
- int n = read(fd, buf, RDBUFSZ);
- if (n < 0) {
- catstrerror(reason, "read", errno);
- goto out;
- }
- if (n == 0)
- break;
-
- data.append(buf, n);
- }
-
- ret = true;
-out:
- if (fd >= 0)
- close(fd);
- return ret;
-}
-
-void path_catslash(string &s) {
- if (s.empty() || s[s.length() - 1] != '/')
- s += '/';
-}
-
-string path_cat(const string &s1, const string &s2) {
- string res = s1;
- path_catslash(res);
- res += s2;
- return res;
-}
-
-bool path_exists(const string& path)
-{
- return access(path.c_str(), 0) == 0;
-}
-
-bool path_isabsolute(const string &path)
-{
- if (!path.empty() && (path[0] == '/'
-#ifdef _WIN32
- || path_isdriveabs(path)
-#endif
- )) {
- return true;
- }
- return false;
-}
-
-string path_home()
-{
- uid_t uid = getuid();
-
- struct passwd *entry = getpwuid(uid);
- if (entry == 0) {
- const char *cp = getenv("HOME");
- if (cp)
- return cp;
- else
- return "/";
- }
-
- string homedir = entry->pw_dir;
- path_catslash(homedir);
- return homedir;
-}
-
-string path_tildexpand(const string &s)
-{
- if (s.empty() || s[0] != '~')
- return s;
- string o = s;
- if (s.length() == 1) {
- o.replace(0, 1, path_home());
- } else if (s[1] == '/') {
- o.replace(0, 2, path_home());
- } else {
- string::size_type pos = s.find('/');
- int l = (pos == string::npos) ? s.length() - 1 : pos - 1;
- struct passwd *entry = getpwnam(s.substr(1, l).c_str());
- if (entry)
- o.replace(0, l+1, entry->pw_dir);
- }
- return o;
-}
-
-bool path_makepath(const string& path, int mode)
-{
- if (path.empty() || path[0] != '/') {
- return false;
- }
- vector<string> vpath;
- stringToTokens(path, vpath, "/", true);
- string npath;
- for (auto it = vpath.begin(); it != vpath.end(); it++) {
- npath += string("/") + *it;
- if (access(npath.c_str(), 0) < 0) {
- if (mkdir(npath.c_str(), mode)) {
- return false;
- }
- }
- }
- return true;
-}
-
-void trimstring(string &s, const char *ws)
-{
- string::size_type pos = s.find_first_not_of(ws);
- if (pos == string::npos) {
- s.clear();
- return;
- }
- s.replace(0, pos, string());
-
- pos = s.find_last_not_of(ws);
- if (pos != string::npos && pos != s.length()-1)
- s.replace(pos+1, string::npos, string());
-}
-
-void stringToTokens(const string& str, vector<string>& tokens,
- const string& delims, bool skipinit)
-{
- string::size_type startPos = 0, pos;
-
- // Skip initial delims, return empty if this eats all.
- if (skipinit &&
- (startPos = str.find_first_not_of(delims, 0)) == string::npos) {
- return;
- }
- while (startPos < str.size()) {
- // Find next delimiter or end of string (end of token)
- pos = str.find_first_of(delims, startPos);
-
- // Add token to the vector and adjust start
- if (pos == string::npos) {
- tokens.push_back(str.substr(startPos));
- break;
- } else if (pos == startPos) {
- // Dont' push empty tokens after first
- if (tokens.empty())
- tokens.push_back(string());
- startPos = ++pos;
- } else {
- tokens.push_back(str.substr(startPos, pos - startPos));
- startPos = ++pos;
- }
- }
-}
-
-
-template <class T> bool stringToStrings(const string &s, T &tokens,
- const string& addseps)
-{
- string current;
- tokens.clear();
- enum states {SPACE, TOKEN, INQUOTE, ESCAPE};
- states state = SPACE;
- for (unsigned int i = 0; i < s.length(); i++) {
- switch (s[i]) {
- case '"':
- switch(state) {
- case SPACE:
- state=INQUOTE; continue;
- case TOKEN:
- current += '"';
- continue;
- case INQUOTE:
- tokens.insert(tokens.end(), current);
- current.clear();
- state = SPACE;
- continue;
- case ESCAPE:
- current += '"';
- state = INQUOTE;
- continue;
- }
- break;
- case '\\':
- switch(state) {
- case SPACE:
- case TOKEN:
- current += '\\';
- state=TOKEN;
- continue;
- case INQUOTE:
- state = ESCAPE;
- continue;
- case ESCAPE:
- current += '\\';
- state = INQUOTE;
- continue;
- }
- break;
-
- case ' ':
- case '\t':
- case '\n':
- case '\r':
- switch(state) {
- case SPACE:
- continue;
- case TOKEN:
- tokens.insert(tokens.end(), current);
- current.clear();
- state = SPACE;
- continue;
- case INQUOTE:
- case ESCAPE:
- current += s[i];
- continue;
- }
- break;
-
- default:
- if (!addseps.empty() && addseps.find(s[i]) != string::npos) {
- switch(state) {
- case ESCAPE:
- state = INQUOTE;
- break;
- case INQUOTE:
- break;
- case SPACE:
- tokens.insert(tokens.end(), string(1, s[i]));
- continue;
- case TOKEN:
- tokens.insert(tokens.end(), current);
- current.erase();
- tokens.insert(tokens.end(), string(1, s[i]));
- state = SPACE;
- continue;
- }
- } else switch(state) {
- case ESCAPE:
- state = INQUOTE;
- break;
- case SPACE:
- state = TOKEN;
- break;
- case TOKEN:
- case INQUOTE:
- break;
- }
- current += s[i];
- }
- }
- switch(state) {
- case SPACE:
- break;
- case TOKEN:
- tokens.insert(tokens.end(), current);
- break;
- case INQUOTE:
- case ESCAPE:
- return false;
- }
- return true;
-}
-
-template bool stringToStrings<vector<string> >(const string &,
- vector<string> &,const string&);
-
// Translate 0-100% MPD volume to UPnP VolumeDB: we do db upnp-encoded
// values from -10240 (0%) to 0 (100%)
int percentodbvalue(int value)
@@ -378,7 +62,7 @@
if (value == 0) {
dbvalue = -10240;
} else {
- float ratio = float(value)*value / 10000.0;
+ float ratio = float(value) * value / 10000.0;
float db = 10 * log10(ratio);
dbvalue = int(256 * db);
}
@@ -397,10 +81,14 @@
int dbvaluetopercent(int dbvalue)
{
float db = float(dbvalue) / 256.0;
- float vol = exp10(db/10);
+ float vol = exp10(db / 10);
int percent = floor(sqrt(vol * 10000.0));
- if (percent < 0) percent = 0;
- if (percent > 100) percent = 100;
+ if (percent < 0) {
+ percent = 0;
+ }
+ if (percent > 100) {
+ percent = 100;
+ }
return percent;
}
@@ -410,23 +98,25 @@
{
static string ns; // null string
unordered_map<string, string>::const_iterator it = im.find(k);
- if (it == im.end())
+ if (it == im.end()) {
return ns;
- else
+ } else {
return it->second;
-}
-
-unordered_map<string, string>
+ }
+}
+
+unordered_map<string, string>
diffmaps(const unordered_map<string, string>& old,
const unordered_map<string, string>& newer)
{
unordered_map<string, string> out;
-
+
for (unordered_map<string, string>::const_iterator it = newer.begin();
- it != newer.end(); it++) {
+ it != newer.end(); it++) {
unordered_map<string, string>::const_iterator ito = old.find(it->first);
- if (ito == old.end() || ito->second.compare(it->second))
+ if (ito == old.end() || ito->second.compare(it->second)) {
out[it->first] = it->second;
+ }
}
return out;
}
@@ -437,71 +127,78 @@
{
ostringstream ss;
ss << "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
- "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
- "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" "
- "xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" "
- "xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\">"
+ "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
+ "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" "
+ "xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" "
+ "xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\">"
<< "<item restricted=\"1\">";
ss << "<orig>mpd</orig>";
- { const string& val = song.title;
+ {
+ const string& val = song.title;
ss << "<dc:title>" << SoapHelp::xmlQuote(val) << "</dc:title>";
}
-
+
// TBD Playlists etc?
ss << "<upnp:class>object.item.audioItem.musicTrack</upnp:class>";
- { const string& val = song.artist;
+ {
+ const string& val = song.artist;
if (!val.empty()) {
string a = SoapHelp::xmlQuote(val);
- ss << "<dc:creator>" << a << "</dc:creator>" <<
- "<upnp:artist>" << a << "</upnp:artist>";
- }
- }
-
- { const string& val = song.album;
+ ss << "<dc:creator>" << a << "</dc:creator>" <<
+ "<upnp:artist>" << a << "</upnp:artist>";
+ }
+ }
+
+ {
+ const string& val = song.album;
if (!val.empty()) {
ss << "<upnp:album>" << SoapHelp::xmlQuote(val) << "</upnp:album>";
}
}
- { const string& val = song.genre;
+ {
+ const string& val = song.genre;
if (!val.empty()) {
ss << "<upnp:genre>" << SoapHelp::xmlQuote(val) << "</upnp:genre>";
}
}
- {string val = song.tracknum;
+ {
+ string val = song.tracknum;
// MPD may return something like xx/yy
string::size_type spos = val.find("/");
- if (spos != string::npos)
+ if (spos != string::npos) {
val = val.substr(0, spos);
- if (!val.empty()) {
- ss << "<upnp:originalTrackNumber>" << val <<
- "</upnp:originalTrackNumber>";
- }
- }
-
- {const string& val = song.artUri;
- if (!val.empty()) {
- ss << "<upnp:albumArtURI>" << SoapHelp::xmlQuote(val) <<
- "</upnp:albumArtURI>";
+ }
+ if (!val.empty()) {
+ ss << "<upnp:originalTrackNumber>" << val <<
+ "</upnp:originalTrackNumber>";
+ }
+ }
+
+ {
+ const string& val = song.artUri;
+ if (!val.empty()) {
+ ss << "<upnp:albumArtURI>" << SoapHelp::xmlQuote(val) <<
+ "</upnp:albumArtURI>";
}
}
// TBD: the res element normally has size, sampleFrequency,
// nrAudioChannels and protocolInfo attributes, which are bogus
// for the moment. partly because MPD does not supply them. And
- // mostly everything is bogus if next is set...
-
- ss << "<res " << "duration=\"" << upnpduration(song.duration_secs * 1000)
+ // mostly everything is bogus if next is set...
+
+ ss << "<res " << "duration=\"" << upnpduration(song.duration_secs * 1000)
<< "\" "
- // Bitrate keeps changing for VBRs and forces events. Keeping
- // it out for now.
- // << "bitrate=\"" << mpds.kbrate << "\" "
+ // Bitrate keeps changing for VBRs and forces events. Keeping
+ // it out for now.
+ // << "bitrate=\"" << mpds.kbrate << "\" "
<< "sampleFrequency=\"44100\" audioChannels=\"2\" "
<< "protocolInfo=\"http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000\""
<< ">"
- << SoapHelp::xmlQuote(song.uri)
+ << SoapHelp::xmlQuote(song.uri)
<< "</res>"
<< "</item></DIDL-Lite>";
return ss.str();
@@ -509,8 +206,9 @@
bool uMetaToUpSong(const string& metadata, UpSong *ups)
{
- if (ups == 0)
+ if (ups == 0) {
return false;
+ }
UPnPDirContent dirc;
if (!dirc.parse(metadata) || dirc.m_items.size() == 0) {
@@ -521,7 +219,7 @@
ups->artist = dobj.f2s("upnp:artist", false);
ups->album = dobj.f2s("upnp:album", false);
ups->title = dobj.m_title;
- string stmp = dobj.f2s("duration", true);
+ string stmp = dobj.f2s("duration", true);
if (!stmp.empty()) {
ups->duration_secs = upnpdurationtos(stmp);
} else {
@@ -540,7 +238,7 @@
regex_t expr;
int err;
const int ERRSIZE = 200;
- char errbuf[ERRSIZE+1];
+ char errbuf[ERRSIZE + 1];
regmatch_t pmatch[10];
if ((err = regcomp(&expr, sexp.c_str(), REG_EXTENDED))) {
@@ -548,7 +246,7 @@
LOGERR("upmpd: regsub1: regcomp() failed: " << errbuf << endl);
return string();
}
-
+
if ((err = regexec(&expr, input.c_str(), 10, pmatch, 0))) {
regerror(err, &expr, errbuf, ERRSIZE);
//LOGDEB("upmpd: regsub1: regexec(" << sexp << ") failed: "
@@ -567,112 +265,3 @@
regfree(&expr);
return out;
}
-
-// We do not want to mess with the pidfile content in the destructor:
-// the lock might still be in use in a child process. In fact as much
-// as we'd like to reset the pid inside the file when we're done, it
-// would be very difficult to do it right and it's probably best left
-// alone.
-Pidfile::~Pidfile()
-{
- if (m_fd >= 0)
- ::close(m_fd);
- m_fd = -1;
-}
-
-pid_t Pidfile::read_pid()
-{
- int fd = ::open(m_path.c_str(), O_RDONLY);
- if (fd == -1)
- return (pid_t)-1;
-
- char buf[16];
- int i = read(fd, buf, sizeof(buf) - 1);
- ::close(fd);
- if (i <= 0)
- return (pid_t)-1;
- buf[i] = '\0';
- char *endptr;
- pid_t pid = strtol(buf, &endptr, 10);
- if (endptr != &buf[i])
- return (pid_t)-1;
- return pid;
-}
-
-int Pidfile::flopen()
-{
- const char *path = m_path.c_str();
- if ((m_fd = ::open(path, O_RDWR|O_CREAT, 0644)) == -1) {
- m_reason = "Open failed: [" + m_path + "]: " + strerror(errno);
- return -1;
- }
-
-#ifdef sun
- struct flock lockdata;
- lockdata.l_start = 0;
- lockdata.l_len = 0;
- lockdata.l_type = F_WRLCK;
- lockdata.l_whence = SEEK_SET;
- if (fcntl(m_fd, F_SETLK, &lockdata) != 0) {
- int serrno = errno;
- (void)::close(m_fd);
- errno = serrno;
- m_reason = "fcntl lock failed";
- return -1;
- }
-#else
- int operation = LOCK_EX | LOCK_NB;
- if (flock(m_fd, operation) == -1) {
- int serrno = errno;
- (void)::close(m_fd);
- errno = serrno;
- m_reason = "flock failed";
- return -1;
- }
-#endif // ! sun
-
- if (ftruncate(m_fd, 0) != 0) {
- /* can't happen [tm] */
- int serrno = errno;
- (void)::close(m_fd);
- errno = serrno;
- m_reason = "ftruncate failed";
- return -1;
- }
- return 0;
-}
-
-pid_t Pidfile::open()
-{
- if (flopen() < 0) {
- return read_pid();
- }
- return (pid_t)0;
-}
-
-int Pidfile::write_pid()
-{
- /* truncate to allow multiple calls */
- if (ftruncate(m_fd, 0) == -1) {
- m_reason = "ftruncate failed";
- return -1;
- }
- char pidstr[20];
- sprintf(pidstr, "%u", int(getpid()));
- lseek(m_fd, 0, 0);
- if (::write(m_fd, pidstr, strlen(pidstr)) != (ssize_t)strlen(pidstr)) {
- m_reason = "write failed";
- return -1;
- }
- return 0;
-}
-
-int Pidfile::close()
-{
- return ::close(m_fd);
-}
-
-int Pidfile::remove()
-{
- return unlink(m_path.c_str());
-}