aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/despotify/DespotifyUtils.cxx153
-rw-r--r--src/lib/despotify/DespotifyUtils.hxx71
-rw-r--r--src/lib/expat/ExpatParser.cxx92
-rw-r--r--src/lib/expat/ExpatParser.hxx129
-rw-r--r--src/lib/icu/Collate.cxx198
-rw-r--r--src/lib/icu/Collate.hxx44
-rw-r--r--src/lib/icu/Error.cxx24
-rw-r--r--src/lib/icu/Error.hxx29
-rw-r--r--src/lib/icu/Init.cxx48
-rw-r--r--src/lib/icu/Init.hxx42
-rw-r--r--src/lib/nfs/Callback.hxx33
-rw-r--r--src/lib/nfs/Cancellable.hxx177
-rw-r--r--src/lib/nfs/Connection.cxx417
-rw-r--r--src/lib/nfs/Connection.hxx176
-rw-r--r--src/lib/nfs/Domain.cxx24
-rw-r--r--src/lib/nfs/Domain.hxx27
-rw-r--r--src/lib/nfs/FileReader.cxx244
-rw-r--r--src/lib/nfs/FileReader.hxx94
-rw-r--r--src/lib/nfs/Glue.cxx69
-rw-r--r--src/lib/nfs/Glue.hxx38
-rw-r--r--src/lib/nfs/Lease.hxx48
-rw-r--r--src/lib/nfs/Manager.cxx57
-rw-r--r--src/lib/nfs/Manager.hxx79
-rw-r--r--src/lib/smbclient/Domain.cxx24
-rw-r--r--src/lib/smbclient/Domain.hxx27
-rw-r--r--src/lib/smbclient/Init.cxx55
-rw-r--r--src/lib/smbclient/Init.hxx33
-rw-r--r--src/lib/smbclient/Mutex.cxx24
-rw-r--r--src/lib/smbclient/Mutex.hxx31
-rw-r--r--src/lib/upnp/Action.hxx56
-rw-r--r--src/lib/upnp/Callback.hxx46
-rw-r--r--src/lib/upnp/ClientInit.cxx93
-rw-r--r--src/lib/upnp/ClientInit.hxx35
-rw-r--r--src/lib/upnp/ContentDirectoryService.cxx93
-rw-r--r--src/lib/upnp/ContentDirectoryService.hxx135
-rw-r--r--src/lib/upnp/Device.cxx134
-rw-r--r--src/lib/upnp/Device.hxx88
-rw-r--r--src/lib/upnp/Discovery.cxx339
-rw-r--r--src/lib/upnp/Discovery.hxx163
-rw-r--r--src/lib/upnp/Domain.cxx23
-rw-r--r--src/lib/upnp/Domain.hxx27
-rw-r--r--src/lib/upnp/Init.cxx73
-rw-r--r--src/lib/upnp/Init.hxx33
-rw-r--r--src/lib/upnp/Util.cxx166
-rw-r--r--src/lib/upnp/Util.hxx46
-rw-r--r--src/lib/upnp/WorkQueue.hxx206
-rw-r--r--src/lib/upnp/ixmlwrap.cxx44
-rw-r--r--src/lib/upnp/ixmlwrap.hxx35
48 files changed, 4342 insertions, 0 deletions
diff --git a/src/lib/despotify/DespotifyUtils.cxx b/src/lib/despotify/DespotifyUtils.cxx
new file mode 100644
index 000000000..47a83e49b
--- /dev/null
+++ b/src/lib/despotify/DespotifyUtils.cxx
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "DespotifyUtils.hxx"
+#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+extern "C" {
+#include <despotify.h>
+}
+
+#include <stdio.h>
+
+const Domain despotify_domain("despotify");
+
+static struct despotify_session *g_session;
+static void (*registered_callbacks[8])(struct despotify_session *,
+ int, void *, void *);
+static void *registered_callback_data[8];
+
+static void
+callback(struct despotify_session* ds, int sig,
+ void *data, gcc_unused void *callback_data)
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
+ void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i];
+ void *cb_data = registered_callback_data[i];
+
+ if (cb)
+ cb(ds, sig, data, cb_data);
+ }
+}
+
+bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *),
+ void *cb_data)
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
+
+ if (!registered_callbacks[i]) {
+ registered_callbacks[i] = cb;
+ registered_callback_data[i] = cb_data;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *))
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
+
+ if (registered_callbacks[i] == cb) {
+ registered_callbacks[i] = nullptr;
+ }
+ }
+}
+
+Tag
+mpd_despotify_tag_from_track(const ds_track &track)
+{
+ char tracknum[20];
+ char comment[80];
+ char date[20];
+
+ if (!track.has_meta_data)
+ return Tag();
+
+ TagBuilder tag;
+ snprintf(tracknum, sizeof(tracknum), "%d", track.tracknumber);
+ snprintf(date, sizeof(date), "%d", track.year);
+ snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted",
+ track.file_bitrate / 1000,
+ track.geo_restricted ? "" : "not ");
+ tag.AddItem(TAG_TITLE, track.title);
+ tag.AddItem(TAG_ARTIST, track.artist->name);
+ tag.AddItem(TAG_TRACK, tracknum);
+ tag.AddItem(TAG_ALBUM, track.album);
+ tag.AddItem(TAG_DATE, date);
+ tag.AddItem(TAG_COMMENT, comment);
+ tag.SetTime(track.length / 1000);
+
+ return tag.Commit();
+}
+
+struct despotify_session *mpd_despotify_get_session(void)
+{
+ const char *user;
+ const char *passwd;
+ bool high_bitrate;
+
+ if (g_session)
+ return g_session;
+
+ user = config_get_string(CONF_DESPOTIFY_USER, nullptr);
+ passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, nullptr);
+ high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true);
+
+ if (user == nullptr || passwd == nullptr) {
+ LogDebug(despotify_domain,
+ "disabling despotify because account is not configured");
+ return nullptr;
+ }
+
+ if (!despotify_init()) {
+ LogWarning(despotify_domain, "Can't initialize despotify");
+ return nullptr;
+ }
+
+ g_session = despotify_init_client(callback, nullptr,
+ high_bitrate, true);
+ if (!g_session) {
+ LogWarning(despotify_domain,
+ "Can't initialize despotify client");
+ return nullptr;
+ }
+
+ if (!despotify_authenticate(g_session, user, passwd)) {
+ LogWarning(despotify_domain,
+ "Can't authenticate despotify session");
+ despotify_exit(g_session);
+ return nullptr;
+ }
+
+ return g_session;
+}
diff --git a/src/lib/despotify/DespotifyUtils.hxx b/src/lib/despotify/DespotifyUtils.hxx
new file mode 100644
index 000000000..835b901a2
--- /dev/null
+++ b/src/lib/despotify/DespotifyUtils.hxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_DESPOTIFY_H
+#define MPD_DESPOTIFY_H
+
+struct Tag;
+struct despotify_session;
+struct ds_track;
+
+extern const class Domain despotify_domain;
+
+/**
+ * Return the current despotify session.
+ *
+ * If the session isn't initialized, this function will initialize
+ * it and connect to Spotify.
+ *
+ * @return a pointer to the despotify session, or nullptr if it can't
+ * be initialized (e.g., if the configuration isn't supplied)
+ */
+struct despotify_session *mpd_despotify_get_session(void);
+
+/**
+ * Create a MPD tags structure from a spotify track
+ *
+ * @param track the track to convert
+ *
+ * @return filled in #Tag structure
+ */
+Tag
+mpd_despotify_tag_from_track(const ds_track &track);
+
+/**
+ * Register a despotify callback.
+ *
+ * Despotify calls this e.g., when a track ends.
+ *
+ * @param cb the callback
+ * @param cb_data the data to pass to the callback
+ *
+ * @return true if the callback could be registered
+ */
+bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *),
+ void *cb_data);
+
+/**
+ * Unregister a despotify callback.
+ *
+ * @param cb the callback to unregister.
+ */
+void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *));
+
+#endif
+
diff --git a/src/lib/expat/ExpatParser.cxx b/src/lib/expat/ExpatParser.cxx
new file mode 100644
index 000000000..c6b1abe76
--- /dev/null
+++ b/src/lib/expat/ExpatParser.cxx
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "ExpatParser.hxx"
+#include "input/InputStream.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <string.h>
+
+static constexpr Domain expat_domain("expat");
+
+void
+ExpatParser::SetError(Error &error)
+{
+ XML_Error code = XML_GetErrorCode(parser);
+ error.Format(expat_domain, int(code), "XML parser failed: %s",
+ XML_ErrorString(code));
+}
+
+bool
+ExpatParser::Parse(const char *data, size_t length, bool is_final,
+ Error &error)
+{
+ bool success = XML_Parse(parser, data, length,
+ is_final) == XML_STATUS_OK;
+ if (!success)
+ SetError(error);
+
+ return success;
+}
+
+bool
+ExpatParser::Parse(InputStream &is, Error &error)
+{
+ assert(is.IsReady());
+
+ while (true) {
+ char buffer[4096];
+ size_t nbytes = is.LockRead(buffer, sizeof(buffer), error);
+ if (nbytes == 0)
+ break;
+
+ if (!Parse(buffer, nbytes, false, error))
+ return false;
+ }
+
+ if (error.IsDefined())
+ return false;
+
+ return Parse("", 0, true, error);
+}
+
+const char *
+ExpatParser::GetAttribute(const XML_Char **atts,
+ const char *name)
+{
+ for (unsigned i = 0; atts[i] != nullptr; i += 2)
+ if (strcmp(atts[i], name) == 0)
+ return atts[i + 1];
+
+ return nullptr;
+}
+
+const char *
+ExpatParser::GetAttributeCase(const XML_Char **atts,
+ const char *name)
+{
+ for (unsigned i = 0; atts[i] != nullptr; i += 2)
+ if (StringEqualsCaseASCII(atts[i], name))
+ return atts[i + 1];
+
+ return nullptr;
+}
diff --git a/src/lib/expat/ExpatParser.hxx b/src/lib/expat/ExpatParser.hxx
new file mode 100644
index 000000000..9d2ac65e5
--- /dev/null
+++ b/src/lib/expat/ExpatParser.hxx
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_EXPAT_HXX
+#define MPD_EXPAT_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <expat.h>
+
+class InputStream;
+class Error;
+
+class ExpatParser final {
+ const XML_Parser parser;
+
+public:
+ ExpatParser(void *userData)
+ :parser(XML_ParserCreate(nullptr)) {
+ XML_SetUserData(parser, userData);
+ }
+
+ ~ExpatParser() {
+ XML_ParserFree(parser);
+ }
+
+ void SetElementHandler(XML_StartElementHandler start,
+ XML_EndElementHandler end) {
+ XML_SetElementHandler(parser, start, end);
+ }
+
+ void SetCharacterDataHandler(XML_CharacterDataHandler charhndl) {
+ XML_SetCharacterDataHandler(parser, charhndl);
+ }
+
+ bool Parse(const char *data, size_t length, bool is_final,
+ Error &error);
+
+ bool Parse(InputStream &is, Error &error);
+
+ gcc_pure
+ static const char *GetAttribute(const XML_Char **atts,
+ const char *name);
+
+ gcc_pure
+ static const char *GetAttributeCase(const XML_Char **atts,
+ const char *name);
+
+private:
+ void SetError(Error &error);
+};
+
+/**
+ * A specialization of #ExpatParser that provides the most common
+ * callbacks as virtual methods.
+ */
+class CommonExpatParser {
+ ExpatParser parser;
+
+public:
+ CommonExpatParser():parser(this) {
+ parser.SetElementHandler(StartElement, EndElement);
+ parser.SetCharacterDataHandler(CharacterData);
+ }
+
+ bool Parse(const char *data, size_t length, bool is_final,
+ Error &error) {
+ return parser.Parse(data, length, is_final, error);
+ }
+
+ bool Parse(InputStream &is, Error &error) {
+ return parser.Parse(is, error);
+ }
+
+ gcc_pure
+ static const char *GetAttribute(const XML_Char **atts,
+ const char *name) {
+ return ExpatParser::GetAttribute(atts, name);
+ }
+
+ gcc_pure
+ static const char *GetAttributeCase(const XML_Char **atts,
+ const char *name) {
+ return ExpatParser::GetAttributeCase(atts, name);
+ }
+
+protected:
+ virtual void StartElement(const XML_Char *name,
+ const XML_Char **atts) = 0;
+ virtual void EndElement(const XML_Char *name) = 0;
+ virtual void CharacterData(const XML_Char *s, int len) = 0;
+
+private:
+ static void XMLCALL StartElement(void *user_data, const XML_Char *name,
+ const XML_Char **atts) {
+ CommonExpatParser &p = *(CommonExpatParser *)user_data;
+ p.StartElement(name, atts);
+ }
+
+ static void XMLCALL EndElement(void *user_data, const XML_Char *name) {
+ CommonExpatParser &p = *(CommonExpatParser *)user_data;
+ p.EndElement(name);
+ }
+
+ static void XMLCALL CharacterData(void *user_data,
+ const XML_Char *s, int len) {
+ CommonExpatParser &p = *(CommonExpatParser *)user_data;
+ p.CharacterData(s, len);
+ }
+};
+
+#endif
diff --git a/src/lib/icu/Collate.cxx b/src/lib/icu/Collate.cxx
new file mode 100644
index 000000000..f6621eb21
--- /dev/null
+++ b/src/lib/icu/Collate.cxx
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "Collate.hxx"
+
+#ifdef HAVE_ICU
+#include "Error.hxx"
+#include "util/WritableBuffer.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <unicode/ucol.h>
+#include <unicode/ustring.h>
+#elif defined(HAVE_GLIB)
+#include <glib.h>
+#else
+#include <algorithm>
+#include <ctype.h>
+#endif
+
+#include <assert.h>
+#include <string.h>
+
+#ifdef HAVE_ICU
+static UCollator *collator;
+#endif
+
+#ifdef HAVE_ICU
+
+bool
+IcuCollateInit(Error &error)
+{
+ assert(collator == nullptr);
+ assert(!error.IsDefined());
+
+ UErrorCode code = U_ZERO_ERROR;
+ collator = ucol_open("", &code);
+ if (collator == nullptr) {
+ error.Format(icu_domain, int(code),
+ "ucol_open() failed: %s", u_errorName(code));
+ return false;
+ }
+
+ return true;
+}
+
+void
+IcuCollateFinish()
+{
+ assert(collator != nullptr);
+
+ ucol_close(collator);
+}
+
+static WritableBuffer<UChar>
+UCharFromUTF8(const char *src)
+{
+ assert(src != nullptr);
+
+ const size_t src_length = strlen(src);
+ const size_t dest_capacity = src_length;
+ UChar *dest = new UChar[dest_capacity];
+
+ UErrorCode error_code = U_ZERO_ERROR;
+ int32_t dest_length;
+ u_strFromUTF8(dest, dest_capacity, &dest_length,
+ src, src_length,
+ &error_code);
+ if (U_FAILURE(error_code)) {
+ delete[] dest;
+ return nullptr;
+ }
+
+ return { dest, size_t(dest_length) };
+}
+
+static WritableBuffer<char>
+UCharToUTF8(ConstBuffer<UChar> src)
+{
+ assert(!src.IsNull());
+
+ /* worst-case estimate */
+ size_t dest_capacity = 4 * src.size;
+
+ char *dest = new char[dest_capacity];
+
+ UErrorCode error_code = U_ZERO_ERROR;
+ int32_t dest_length;
+ u_strToUTF8(dest, dest_capacity, &dest_length, src.data, src.size,
+ &error_code);
+ if (U_FAILURE(error_code)) {
+ delete[] dest;
+ return nullptr;
+ }
+
+ return { dest, size_t(dest_length) };
+}
+
+#endif
+
+gcc_pure
+int
+IcuCollate(const char *a, const char *b)
+{
+ assert(a != nullptr);
+ assert(b != nullptr);
+
+#ifdef HAVE_ICU
+ assert(collator != nullptr);
+
+#if U_ICU_VERSION_MAJOR_NUM >= 50
+ UErrorCode code = U_ZERO_ERROR;
+ return (int)ucol_strcollUTF8(collator, a, -1, b, -1, &code);
+#else
+ /* fall back to ucol_strcoll() */
+
+ const auto au = UCharFromUTF8(a);
+ const auto bu = UCharFromUTF8(b);
+
+ int result = !au.IsNull() && !bu.IsNull()
+ ? (int)ucol_strcoll(collator, au.data, au.size,
+ bu.data, bu.size)
+ : strcasecmp(a, b);
+
+ delete[] au.data;
+ delete[] bu.data;
+
+ return result;
+#endif
+
+#elif defined(HAVE_GLIB)
+ return g_utf8_collate(a, b);
+#else
+ return strcasecmp(a, b);
+#endif
+}
+
+std::string
+IcuCaseFold(const char *src)
+{
+#ifdef HAVE_ICU
+ assert(collator != nullptr);
+ assert(src != nullptr);
+
+ const auto u = UCharFromUTF8(src);
+ if (u.IsNull())
+ return std::string(src);
+
+ size_t folded_capacity = u.size * 2u;
+ UChar *folded = new UChar[folded_capacity];
+
+ UErrorCode error_code = U_ZERO_ERROR;
+ size_t folded_length = u_strFoldCase(folded, folded_capacity,
+ u.data, u.size,
+ U_FOLD_CASE_DEFAULT,
+ &error_code);
+ delete[] u.data;
+ if (folded_length == 0 || error_code != U_ZERO_ERROR) {
+ delete[] folded;
+ return std::string(src);
+ }
+
+ auto result2 = UCharToUTF8({folded, folded_length});
+ delete[] folded;
+ if (result2.IsNull())
+ return std::string(src);
+
+ std::string result(result2.data, result2.size);
+ delete[] result2.data;
+#elif defined(HAVE_GLIB)
+ char *tmp = g_utf8_casefold(src, -1);
+ std::string result(tmp);
+ g_free(tmp);
+#else
+ std::string result(src);
+ std::transform(result.begin(), result.end(), result.begin(), tolower);
+#endif
+ return result;
+}
+
diff --git a/src/lib/icu/Collate.hxx b/src/lib/icu/Collate.hxx
new file mode 100644
index 000000000..8ae8de46a
--- /dev/null
+++ b/src/lib/icu/Collate.hxx
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_ICU_COLLATE_HXX
+#define MPD_ICU_COLLATE_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <string>
+
+class Error;
+
+bool
+IcuCollateInit(Error &error);
+
+void
+IcuCollateFinish();
+
+gcc_pure gcc_nonnull_all
+int
+IcuCollate(const char *a, const char *b);
+
+gcc_pure gcc_nonnull_all
+std::string
+IcuCaseFold(const char *src);
+
+#endif
diff --git a/src/lib/icu/Error.cxx b/src/lib/icu/Error.cxx
new file mode 100644
index 000000000..1fef078ac
--- /dev/null
+++ b/src/lib/icu/Error.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "Error.hxx"
+#include "util/Domain.hxx"
+
+const Domain icu_domain("icu");
diff --git a/src/lib/icu/Error.hxx b/src/lib/icu/Error.hxx
new file mode 100644
index 000000000..e96667f57
--- /dev/null
+++ b/src/lib/icu/Error.hxx
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_ICU_ERROR_HXX
+#define MPD_ICU_ERROR_HXX
+
+#include "check.h"
+
+class Domain;
+
+extern const Domain icu_domain;
+
+#endif
diff --git a/src/lib/icu/Init.cxx b/src/lib/icu/Init.cxx
new file mode 100644
index 000000000..1d0ad0777
--- /dev/null
+++ b/src/lib/icu/Init.cxx
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "Init.hxx"
+#include "Error.hxx"
+#include "Collate.hxx"
+#include "util/Error.hxx"
+
+#include <unicode/uclean.h>
+
+bool
+IcuInit(Error &error)
+{
+ UErrorCode code = U_ZERO_ERROR;
+ u_init(&code);
+ if (U_FAILURE(code)) {
+ error.Format(icu_domain, int(code),
+ "u_init() failed: %s", u_errorName(code));
+ return false;
+ }
+
+ return IcuCollateInit(error);
+}
+
+void
+IcuFinish()
+{
+ IcuCollateFinish();
+
+ u_cleanup();
+}
diff --git a/src/lib/icu/Init.hxx b/src/lib/icu/Init.hxx
new file mode 100644
index 000000000..9f585e2bd
--- /dev/null
+++ b/src/lib/icu/Init.hxx
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_ICU_INIT_HXX
+#define MPD_ICU_INIT_HXX
+
+#include "check.h"
+
+class Error;
+
+#ifdef HAVE_ICU
+
+bool
+IcuInit(Error &error);
+
+void
+IcuFinish();
+
+#else
+
+static inline bool IcuInit(Error &) { return true; }
+static inline void IcuFinish() {}
+
+#endif
+
+#endif
diff --git a/src/lib/nfs/Callback.hxx b/src/lib/nfs/Callback.hxx
new file mode 100644
index 000000000..ae82ecc3c
--- /dev/null
+++ b/src/lib/nfs/Callback.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_NFS_CALLBACK_HXX
+#define MPD_NFS_CALLBACK_HXX
+
+#include "check.h"
+
+class Error;
+
+class NfsCallback {
+public:
+ virtual void OnNfsCallback(unsigned status, void *data) = 0;
+ virtual void OnNfsError(Error &&error) = 0;
+};
+
+#endif
diff --git a/src/lib/nfs/Cancellable.hxx b/src/lib/nfs/Cancellable.hxx
new file mode 100644
index 000000000..50762b582
--- /dev/null
+++ b/src/lib/nfs/Cancellable.hxx
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_NFS_CANCELLABLE_HXX
+#define MPD_NFS_CANCELLABLE_HXX
+
+#include "Compiler.h"
+
+#include <list>
+#include <algorithm>
+
+#include <assert.h>
+
+template<typename T>
+class CancellablePointer {
+public:
+ typedef T *pointer_type;
+ typedef T &reference_type;
+ typedef const T &const_reference_type;
+
+private:
+ pointer_type p;
+
+public:
+ explicit constexpr CancellablePointer(reference_type _p):p(&_p) {}
+
+ CancellablePointer(const CancellablePointer &) = delete;
+
+ constexpr bool IsCancelled() const {
+ return p == nullptr;
+ }
+
+ void Cancel() {
+ assert(!IsCancelled());
+
+ p = nullptr;
+ }
+
+ reference_type Get() {
+ assert(p != nullptr);
+
+ return *p;
+ }
+
+ constexpr bool Is(const_reference_type other) const {
+ return p == &other;
+ }
+};
+
+template<typename T, typename CT=CancellablePointer<T>>
+class CancellableList {
+public:
+ typedef typename CT::reference_type reference_type;
+ typedef typename CT::const_reference_type const_reference_type;
+
+private:
+ typedef std::list<CT> List;
+ typedef typename List::iterator iterator;
+ typedef typename List::const_iterator const_iterator;
+ List list;
+
+ class MatchPointer {
+ const_reference_type p;
+
+ public:
+ explicit constexpr MatchPointer(const_reference_type _p)
+ :p(_p) {}
+
+ constexpr bool operator()(const CT &a) const {
+ return a.Is(p);
+ }
+ };
+
+ gcc_pure
+ iterator Find(reference_type p) {
+ return std::find_if(list.begin(), list.end(), MatchPointer(p));
+ }
+
+ gcc_pure
+ const_iterator Find(const_reference_type p) const {
+ return std::find_if(list.begin(), list.end(), MatchPointer(p));
+ }
+
+ class MatchReference {
+ const CT &c;
+
+ public:
+ constexpr explicit MatchReference(const CT &_c):c(_c) {}
+
+ gcc_pure
+ bool operator()(const CT &a) const {
+ return &a == &c;
+ }
+ };
+
+ gcc_pure
+ iterator Find(CT &c) {
+ return std::find_if(list.begin(), list.end(),
+ MatchReference(c));
+ }
+
+ gcc_pure
+ const_iterator Find(const CT &c) const {
+ return std::find_if(list.begin(), list.end(),
+ MatchReference(c));
+ }
+
+public:
+#ifndef NDEBUG
+ gcc_pure
+ bool IsEmpty() const {
+ for (const auto &c : list)
+ if (!c.IsCancelled())
+ return false;
+
+ return true;
+ }
+#endif
+
+ gcc_pure
+ bool Contains(const_reference_type p) const {
+ return Find(p) != list.end();
+ }
+
+ template<typename... Args>
+ CT &Add(reference_type p, Args&&... args) {
+ assert(Find(p) == list.end());
+
+ list.emplace_back(p, std::forward<Args>(args)...);
+ return list.back();
+ }
+
+ void RemoveLast() {
+ list.pop_back();
+ }
+
+ bool RemoveOptional(CT &ct) {
+ auto i = Find(ct);
+ if (i == list.end())
+ return false;
+
+ list.erase(i);
+ return true;
+ }
+
+ void Remove(CT &ct) {
+ auto i = Find(ct);
+ assert(i != list.end());
+
+ list.erase(i);
+ }
+
+ void Cancel(reference_type p) {
+ auto i = Find(p);
+ assert(i != list.end());
+
+ i->Cancel();
+ }
+};
+
+#endif
diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx
new file mode 100644
index 000000000..a2f0cbb45
--- /dev/null
+++ b/src/lib/nfs/Connection.cxx
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "Connection.hxx"
+#include "Lease.hxx"
+#include "Domain.hxx"
+#include "Callback.hxx"
+#include "system/fd_util.h"
+#include "util/Error.hxx"
+#include "event/Call.hxx"
+
+extern "C" {
+#include <nfsc/libnfs.h>
+}
+
+#include <utility>
+
+#include <poll.h> /* for POLLIN, POLLOUT */
+
+inline bool
+NfsConnection::CancellableCallback::Open(nfs_context *ctx,
+ const char *path, int flags,
+ Error &error)
+{
+ int result = nfs_open_async(ctx, path, flags,
+ Callback, this);
+ if (result < 0) {
+ error.Format(nfs_domain, "nfs_open_async() failed: %s",
+ nfs_get_error(ctx));
+ return false;
+ }
+
+ return true;
+}
+
+inline bool
+NfsConnection::CancellableCallback::Stat(nfs_context *ctx,
+ struct nfsfh *fh,
+ Error &error)
+{
+ int result = nfs_fstat_async(ctx, fh, Callback, this);
+ if (result < 0) {
+ error.Format(nfs_domain, "nfs_fstat_async() failed: %s",
+ nfs_get_error(ctx));
+ return false;
+ }
+
+ return true;
+}
+
+inline bool
+NfsConnection::CancellableCallback::Read(nfs_context *ctx, struct nfsfh *fh,
+ uint64_t offset, size_t size,
+ Error &error)
+{
+ int result = nfs_pread_async(ctx, fh, offset, size, Callback, this);
+ if (result < 0) {
+ error.Format(nfs_domain, "nfs_pread_async() failed: %s",
+ nfs_get_error(ctx));
+ return false;
+ }
+
+ return true;
+}
+
+inline void
+NfsConnection::CancellableCallback::Callback(int err, void *data)
+{
+ if (!IsCancelled()) {
+ NfsCallback &cb = Get();
+
+ connection.callbacks.Remove(*this);
+
+ if (err >= 0)
+ cb.OnNfsCallback((unsigned)err, data);
+ else
+ cb.OnNfsError(Error(nfs_domain, err,
+ (const char *)data));
+ } else {
+ connection.callbacks.Remove(*this);
+ }
+}
+
+void
+NfsConnection::CancellableCallback::Callback(int err,
+ gcc_unused struct nfs_context *nfs,
+ void *data, void *private_data)
+{
+ CancellableCallback &c = *(CancellableCallback *)private_data;
+ c.Callback(err, data);
+}
+
+static constexpr unsigned
+libnfs_to_events(int i)
+{
+ return ((i & POLLIN) ? SocketMonitor::READ : 0) |
+ ((i & POLLOUT) ? SocketMonitor::WRITE : 0);
+}
+
+static constexpr int
+events_to_libnfs(unsigned i)
+{
+ return ((i & SocketMonitor::READ) ? POLLIN : 0) |
+ ((i & SocketMonitor::WRITE) ? POLLOUT : 0);
+}
+
+NfsConnection::~NfsConnection()
+{
+ assert(new_leases.empty());
+ assert(active_leases.empty());
+ assert(callbacks.IsEmpty());
+
+ if (context != nullptr)
+ BlockingCall(SocketMonitor::GetEventLoop(), [this](){
+ DestroyContext();
+ });
+}
+
+void
+NfsConnection::AddLease(NfsLease &lease)
+{
+ {
+ const ScopeLock protect(mutex);
+ new_leases.push_back(&lease);
+ }
+
+ DeferredMonitor::Schedule();
+}
+
+void
+NfsConnection::RemoveLease(NfsLease &lease)
+{
+ const ScopeLock protect(mutex);
+
+ new_leases.remove(&lease);
+ active_leases.remove(&lease);
+}
+
+bool
+NfsConnection::Open(const char *path, int flags, NfsCallback &callback,
+ Error &error)
+{
+ assert(!callbacks.Contains(callback));
+
+ auto &c = callbacks.Add(callback, *this);
+ if (!c.Open(context, path, flags, error)) {
+ callbacks.RemoveLast();
+ return false;
+ }
+
+ ScheduleSocket();
+ return true;
+}
+
+bool
+NfsConnection::Stat(struct nfsfh *fh, NfsCallback &callback, Error &error)
+{
+ assert(!callbacks.Contains(callback));
+
+ auto &c = callbacks.Add(callback, *this);
+ if (!c.Stat(context, fh, error)) {
+ callbacks.RemoveLast();
+ return false;
+ }
+
+ ScheduleSocket();
+ return true;
+}
+
+bool
+NfsConnection::Read(struct nfsfh *fh, uint64_t offset, size_t size,
+ NfsCallback &callback, Error &error)
+{
+ assert(!callbacks.Contains(callback));
+
+ auto &c = callbacks.Add(callback, *this);
+ if (!c.Read(context, fh, offset, size, error)) {
+ callbacks.RemoveLast();
+ return false;
+ }
+
+ ScheduleSocket();
+ return true;
+}
+
+void
+NfsConnection::Cancel(NfsCallback &callback)
+{
+ callbacks.Cancel(callback);
+}
+
+static void
+DummyCallback(int, struct nfs_context *, void *, void *)
+{
+}
+
+void
+NfsConnection::Close(struct nfsfh *fh)
+{
+ nfs_close_async(context, fh, DummyCallback, nullptr);
+ ScheduleSocket();
+}
+
+void
+NfsConnection::DestroyContext()
+{
+ assert(context != nullptr);
+
+ SocketMonitor::Cancel();
+ nfs_destroy_context(context);
+ context = nullptr;
+}
+
+void
+NfsConnection::ScheduleSocket()
+{
+ assert(context != nullptr);
+
+ if (!SocketMonitor::IsDefined()) {
+ int _fd = nfs_get_fd(context);
+ fd_set_cloexec(_fd, true);
+ SocketMonitor::Open(_fd);
+ }
+
+ SocketMonitor::Schedule(libnfs_to_events(nfs_which_events(context)));
+}
+
+bool
+NfsConnection::OnSocketReady(unsigned flags)
+{
+ bool closed = false;
+
+ const bool was_mounted = mount_finished;
+ if (!mount_finished)
+ /* until the mount is finished, the NFS client may use
+ various sockets, therefore we unregister and
+ re-register it each time */
+ SocketMonitor::Steal();
+
+ assert(!in_event);
+ in_event = true;
+
+ assert(!in_service);
+ in_service = true;
+ postponed_destroy = false;
+
+ int result = nfs_service(context, events_to_libnfs(flags));
+
+ assert(context != nullptr);
+ assert(in_service);
+ in_service = false;
+
+ if (postponed_destroy) {
+ /* somebody has called nfs_client_free() while we were inside
+ nfs_service() */
+ const ScopeLock protect(mutex);
+ DestroyContext();
+ closed = true;
+ // TODO? nfs_client_cleanup_files(client);
+ } else if (!was_mounted && mount_finished) {
+ const ScopeLock protect(mutex);
+
+ if (postponed_mount_error.IsDefined()) {
+ DestroyContext();
+ closed = true;
+ BroadcastMountError(std::move(postponed_mount_error));
+ } else if (result == 0)
+ BroadcastMountSuccess();
+ } else if (result < 0) {
+ /* the connection has failed */
+ Error error;
+ error.Format(nfs_domain, "NFS connection has failed: %s",
+ nfs_get_error(context));
+
+ const ScopeLock protect(mutex);
+
+ DestroyContext();
+ closed = true;
+
+ if (!mount_finished)
+ BroadcastMountError(std::move(error));
+ else
+ BroadcastError(std::move(error));
+ }
+
+ assert(in_event);
+ in_event = false;
+
+ if (context != nullptr)
+ ScheduleSocket();
+
+ return !closed;
+}
+
+inline void
+NfsConnection::MountCallback(int status, gcc_unused nfs_context *nfs,
+ gcc_unused void *data)
+{
+ assert(context == nfs);
+
+ mount_finished = true;
+
+ if (status < 0) {
+ postponed_mount_error.Set(nfs_domain, status,
+ "nfs_mount_async() failed");
+ return;
+ }
+}
+
+void
+NfsConnection::MountCallback(int status, nfs_context *nfs, void *data,
+ void *private_data)
+{
+ NfsConnection *c = (NfsConnection *)private_data;
+
+ c->MountCallback(status, nfs, data);
+}
+
+inline bool
+NfsConnection::MountInternal(Error &error)
+{
+ if (context != nullptr)
+ return true;
+
+ context = nfs_init_context();
+ if (context == nullptr) {
+ error.Set(nfs_domain, "nfs_init_context() failed");
+ return false;
+ }
+
+ postponed_mount_error.Clear();
+ mount_finished = false;
+ in_service = false;
+ in_event = false;
+
+ if (nfs_mount_async(context, server.c_str(), export_name.c_str(),
+ MountCallback, this) != 0) {
+ error.Format(nfs_domain,
+ "nfs_mount_async() failed: %s",
+ nfs_get_error(context));
+ nfs_destroy_context(context);
+ context = nullptr;
+ return false;
+ }
+
+ ScheduleSocket();
+ return true;
+}
+
+void
+NfsConnection::BroadcastMountSuccess()
+{
+ while (!new_leases.empty()) {
+ auto i = new_leases.begin();
+ active_leases.splice(active_leases.end(), new_leases, i);
+ (*i)->OnNfsConnectionReady();
+ }
+}
+
+void
+NfsConnection::BroadcastMountError(Error &&error)
+{
+ while (!new_leases.empty()) {
+ auto l = new_leases.front();
+ new_leases.pop_front();
+ l->OnNfsConnectionFailed(error);
+ }
+
+ OnNfsConnectionError(std::move(error));
+}
+
+void
+NfsConnection::BroadcastError(Error &&error)
+{
+ while (!active_leases.empty()) {
+ auto l = active_leases.front();
+ active_leases.pop_front();
+ l->OnNfsConnectionDisconnected(error);
+ }
+
+ BroadcastMountError(std::move(error));
+}
+
+void
+NfsConnection::RunDeferred()
+{
+ {
+ Error error;
+ if (!MountInternal(error)) {
+ const ScopeLock protect(mutex);
+ BroadcastMountError(std::move(error));
+ return;
+ }
+ }
+
+ if (mount_finished) {
+ const ScopeLock protect(mutex);
+ BroadcastMountSuccess();
+ }
+}
diff --git a/src/lib/nfs/Connection.hxx b/src/lib/nfs/Connection.hxx
new file mode 100644
index 000000000..cb4126d25
--- /dev/null
+++ b/src/lib/nfs/Connection.hxx
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_NFS_CONNECTION_HXX
+#define MPD_NFS_CONNECTION_HXX
+
+#include "Lease.hxx"
+#include "Cancellable.hxx"
+#include "thread/Mutex.hxx"
+#include "event/SocketMonitor.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "util/Error.hxx"
+
+#include <string>
+#include <list>
+
+struct nfs_context;
+class NfsCallback;
+
+/**
+ * An asynchronous connection to a NFS server.
+ */
+class NfsConnection : SocketMonitor, DeferredMonitor {
+ class CancellableCallback : public CancellablePointer<NfsCallback> {
+ NfsConnection &connection;
+
+ public:
+ explicit constexpr CancellableCallback(NfsCallback &_callback,
+ NfsConnection &_connection)
+ :CancellablePointer<NfsCallback>(_callback),
+ connection(_connection) {}
+
+ bool Open(nfs_context *context, const char *path, int flags,
+ Error &error);
+ bool Stat(nfs_context *context, struct nfsfh *fh,
+ Error &error);
+ bool Read(nfs_context *context, struct nfsfh *fh,
+ uint64_t offset, size_t size,
+ Error &error);
+
+ private:
+ static void Callback(int err, struct nfs_context *nfs,
+ void *data, void *private_data);
+ void Callback(int err, void *data);
+ };
+
+ std::string server, export_name;
+
+ nfs_context *context;
+
+ Mutex mutex;
+
+ typedef std::list<NfsLease *> LeaseList;
+ LeaseList new_leases, active_leases;
+
+ typedef CancellableList<NfsCallback, CancellableCallback> CallbackList;
+ CallbackList callbacks;
+
+ Error postponed_mount_error;
+
+ /**
+ * True when nfs_service() is being called. During that,
+ * nfs_client_free() is postponed, or libnfs will crash. See
+ * #postponed_destroy.
+ */
+ bool in_service;
+
+ /**
+ * True when OnSocketReady() is being called. During that,
+ * event updates are omitted.
+ */
+ bool in_event;
+
+ /**
+ * True when nfs_client_free() has been called while #in_service
+ * was true.
+ */
+ bool postponed_destroy;
+
+ bool mount_finished;
+
+public:
+ gcc_nonnull_all
+ NfsConnection(EventLoop &_loop,
+ const char *_server, const char *_export_name)
+ :SocketMonitor(_loop), DeferredMonitor(_loop),
+ server(_server), export_name(_export_name),
+ context(nullptr) {}
+
+#if defined(__GNUC__) && !defined(__clang__) && !GCC_CHECK_VERSION(4,8)
+ /* needed for NfsManager::GetConnection() due to lack of
+ std::map::emplace() */
+ NfsConnection(NfsConnection &&other)
+ :SocketMonitor(((SocketMonitor &)other).GetEventLoop()),
+ DeferredMonitor(((DeferredMonitor &)other).GetEventLoop()),
+ server(std::move(other.server)),
+ export_name(std::move(other.export_name)),
+ context(nullptr) {
+ assert(other.context == nullptr);
+ assert(other.new_leases.empty());
+ assert(other.active_leases.empty());
+ assert(other.callbacks.IsEmpty());
+ }
+#endif
+
+ ~NfsConnection();
+
+ gcc_pure
+ const char *GetServer() const {
+ return server.c_str();
+ }
+
+ gcc_pure
+ const char *GetExportName() const {
+ return export_name.c_str();
+ }
+
+ /**
+ * Ensure that the connection is established. The connection
+ * is kept up while at least one #NfsLease is registered.
+ *
+ * This method is thread-safe. However, #NfsLease's methods
+ * will be invoked from within the #EventLoop's thread.
+ */
+ void AddLease(NfsLease &lease);
+ void RemoveLease(NfsLease &lease);
+
+ bool Open(const char *path, int flags, NfsCallback &callback,
+ Error &error);
+ bool Stat(struct nfsfh *fh, NfsCallback &callback, Error &error);
+ bool Read(struct nfsfh *fh, uint64_t offset, size_t size,
+ NfsCallback &callback, Error &error);
+ void Cancel(NfsCallback &callback);
+
+ void Close(struct nfsfh *fh);
+
+protected:
+ virtual void OnNfsConnectionError(Error &&error) = 0;
+
+private:
+ void DestroyContext();
+ bool MountInternal(Error &error);
+ void BroadcastMountSuccess();
+ void BroadcastMountError(Error &&error);
+ void BroadcastError(Error &&error);
+
+ static void MountCallback(int status, nfs_context *nfs, void *data,
+ void *private_data);
+ void MountCallback(int status, nfs_context *nfs, void *data);
+
+ void ScheduleSocket();
+
+ /* virtual methods from SocketMonitor */
+ virtual bool OnSocketReady(unsigned flags) override;
+
+ /* virtual methods from DeferredMonitor */
+ virtual void RunDeferred() override;
+};
+
+#endif
diff --git a/src/lib/nfs/Domain.cxx b/src/lib/nfs/Domain.cxx
new file mode 100644
index 000000000..fefe0dbf3
--- /dev/null
+++ b/src/lib/nfs/Domain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain nfs_domain("nfs");
diff --git a/src/lib/nfs/Domain.hxx b/src/lib/nfs/Domain.hxx
new file mode 100644
index 000000000..6730b92e1
--- /dev/null
+++ b/src/lib/nfs/Domain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_NFS_DOMAIN_HXX
+#define MPD_NFS_DOMAIN_HXX
+
+class Domain;
+
+extern const Domain nfs_domain;
+
+#endif
diff --git a/src/lib/nfs/FileReader.cxx b/src/lib/nfs/FileReader.cxx
new file mode 100644
index 000000000..52d951fa6
--- /dev/null
+++ b/src/lib/nfs/FileReader.cxx
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "FileReader.hxx"
+#include "Glue.hxx"
+#include "Connection.hxx"
+#include "Domain.hxx"
+#include "event/Call.hxx"
+#include "IOThread.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+
+#include <utility>
+
+#include <assert.h>
+#include <string.h>
+#include <fcntl.h>
+
+NfsFileReader::NfsFileReader()
+ :DeferredMonitor(io_thread_get()), state(State::INITIAL)
+{
+}
+
+NfsFileReader::~NfsFileReader()
+{
+ assert(state == State::INITIAL);
+}
+
+void
+NfsFileReader::Close()
+{
+ if (state == State::INITIAL)
+ return;
+
+ if (state == State::DEFER) {
+ state = State::INITIAL;
+ DeferredMonitor::Cancel();
+ return;
+ }
+
+ connection->RemoveLease(*this);
+
+ if (state > State::MOUNT && state != State::IDLE)
+ connection->Cancel(*this);
+
+ if (state > State::OPEN)
+ connection->Close(fh);
+
+ state = State::INITIAL;
+}
+
+void
+NfsFileReader::DeferClose()
+{
+ BlockingCall(io_thread_get(), [this](){ Close(); });
+}
+
+bool
+NfsFileReader::Open(const char *uri, Error &error)
+{
+ assert(state == State::INITIAL);
+
+ if (!StringStartsWith(uri, "nfs://")) {
+ error.Set(nfs_domain, "Malformed nfs:// URI");
+ return false;
+ }
+
+ uri += 6;
+
+ const char *slash = strchr(uri, '/');
+ if (slash == nullptr) {
+ error.Set(nfs_domain, "Malformed nfs:// URI");
+ return false;
+ }
+
+ server = std::string(uri, slash);
+
+ uri = slash;
+ slash = strrchr(uri + 1, '/');
+ if (slash == nullptr || slash[1] == 0) {
+ error.Set(nfs_domain, "Malformed nfs:// URI");
+ return false;
+ }
+
+ export_name = std::string(uri, slash);
+ path = slash;
+
+ state = State::DEFER;
+ DeferredMonitor::Schedule();
+ return true;
+}
+
+bool
+NfsFileReader::Read(uint64_t offset, size_t size, Error &error)
+{
+ assert(state == State::IDLE);
+
+ if (!connection->Read(fh, offset, size, *this, error))
+ return false;
+
+ state = State::READ;
+ return true;
+}
+
+void
+NfsFileReader::CancelRead()
+{
+ if (state == State::READ) {
+ connection->Cancel(*this);
+ state = State::IDLE;
+ }
+}
+
+void
+NfsFileReader::OnNfsConnectionReady()
+{
+ assert(state == State::MOUNT);
+
+ Error error;
+ if (!connection->Open(path, O_RDONLY, *this, error)) {
+ OnNfsFileError(std::move(error));
+ return;
+ }
+
+ state = State::OPEN;
+}
+
+void
+NfsFileReader::OnNfsConnectionFailed(const Error &error)
+{
+ assert(state == State::MOUNT);
+
+ Error copy;
+ copy.Set(error);
+ OnNfsFileError(std::move(copy));
+}
+
+void
+NfsFileReader::OnNfsConnectionDisconnected(const Error &error)
+{
+ assert(state > State::MOUNT);
+
+ state = State::INITIAL;
+
+ Error copy;
+ copy.Set(error);
+ OnNfsFileError(std::move(copy));
+}
+
+inline void
+NfsFileReader::OpenCallback(nfsfh *_fh)
+{
+ assert(state == State::OPEN);
+ assert(connection != nullptr);
+ assert(_fh != nullptr);
+
+ fh = _fh;
+
+ Error error;
+ if (!connection->Stat(fh, *this, error)) {
+ OnNfsFileError(std::move(error));
+ return;
+ }
+
+ state = State::STAT;
+}
+
+inline void
+NfsFileReader::StatCallback(const struct stat *st)
+{
+ assert(state == State::STAT);
+ assert(connection != nullptr);
+ assert(fh != nullptr);
+ assert(st != nullptr);
+
+ if (!S_ISREG(st->st_mode)) {
+ OnNfsFileError(Error(nfs_domain, "Not a regular file"));
+ return;
+ }
+
+ state = State::IDLE;
+
+ OnNfsFileOpen(st->st_size);
+}
+
+void
+NfsFileReader::OnNfsCallback(unsigned status, void *data)
+{
+ switch (state) {
+ case State::INITIAL:
+ case State::DEFER:
+ case State::MOUNT:
+ case State::IDLE:
+ assert(false);
+ gcc_unreachable();
+
+ case State::OPEN:
+ OpenCallback((struct nfsfh *)data);
+ break;
+
+ case State::STAT:
+ StatCallback((const struct stat *)data);
+ break;
+
+ case State::READ:
+ state = State::IDLE;
+ OnNfsFileRead(data, status);
+ break;
+ }
+}
+
+void
+NfsFileReader::OnNfsError(Error &&error)
+{
+ OnNfsFileError(std::move(error));
+}
+
+void
+NfsFileReader::RunDeferred()
+{
+ assert(state == State::DEFER);
+
+ state = State::MOUNT;
+
+ connection = &nfs_get_connection(server.c_str(), export_name.c_str());
+ connection->AddLease(*this);
+}
diff --git a/src/lib/nfs/FileReader.hxx b/src/lib/nfs/FileReader.hxx
new file mode 100644
index 000000000..7f43e0ecf
--- /dev/null
+++ b/src/lib/nfs/FileReader.hxx
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_NFS_FILE_READER_HXX
+#define MPD_NFS_FILE_READER_HXX
+
+#include "check.h"
+#include "Lease.hxx"
+#include "Callback.hxx"
+#include "event/DeferredMonitor.hxx"
+
+#include <string>
+
+#include <stdint.h>
+#include <stddef.h>
+#include <sys/stat.h>
+
+struct nfsfh;
+class NfsConnection;
+
+class NfsFileReader : NfsLease, NfsCallback, DeferredMonitor {
+ enum class State {
+ INITIAL,
+ DEFER,
+ MOUNT,
+ OPEN,
+ STAT,
+ READ,
+ IDLE,
+ };
+
+ State state;
+
+ std::string server, export_name;
+ const char *path;
+
+ NfsConnection *connection;
+
+ nfsfh *fh;
+
+public:
+ NfsFileReader();
+ ~NfsFileReader();
+
+ void Close();
+ void DeferClose();
+
+ bool Open(const char *uri, Error &error);
+ bool Read(uint64_t offset, size_t size, Error &error);
+ void CancelRead();
+
+ bool IsIdle() const {
+ return state == State::IDLE;
+ }
+
+protected:
+ virtual void OnNfsFileOpen(uint64_t size) = 0;
+ virtual void OnNfsFileRead(const void *data, size_t size) = 0;
+ virtual void OnNfsFileError(Error &&error) = 0;
+
+private:
+ void OpenCallback(nfsfh *_fh);
+ void StatCallback(const struct stat *st);
+
+ /* virtual methods from NfsLease */
+ void OnNfsConnectionReady() final;
+ void OnNfsConnectionFailed(const Error &error) final;
+ void OnNfsConnectionDisconnected(const Error &error) final;
+
+ /* virtual methods from NfsCallback */
+ void OnNfsCallback(unsigned status, void *data) final;
+ void OnNfsError(Error &&error) final;
+
+ /* virtual methods from DeferredMonitor */
+ void RunDeferred() final;
+};
+
+#endif
diff --git a/src/lib/nfs/Glue.cxx b/src/lib/nfs/Glue.cxx
new file mode 100644
index 000000000..c043f06c6
--- /dev/null
+++ b/src/lib/nfs/Glue.cxx
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "Glue.hxx"
+#include "Manager.hxx"
+#include "IOThread.hxx"
+#include "util/Manual.hxx"
+
+class NfsGlue {
+ NfsManager manager;
+
+public:
+ NfsGlue(EventLoop &_loop)
+ :manager(_loop) {}
+
+ ~NfsGlue() {
+ //assert(open_uri.empty());
+ }
+
+ NfsConnection &GetConnection(const char *server, const char *export_name) {
+ return manager.GetConnection(server, export_name);
+ }
+};
+
+static Manual<NfsGlue> nfs_glue;
+static unsigned in_use;
+
+void
+nfs_init()
+{
+ if (in_use++ > 0)
+ return;
+
+ nfs_glue.Construct(io_thread_get());
+}
+
+void
+nfs_finish()
+{
+ assert(in_use > 0);
+
+ if (--in_use > 0)
+ return;
+
+ nfs_glue.Destruct();
+}
+
+NfsConnection &
+nfs_get_connection(const char *server, const char *export_name)
+{
+ return nfs_glue->GetConnection(server, export_name);
+}
diff --git a/src/lib/nfs/Glue.hxx b/src/lib/nfs/Glue.hxx
new file mode 100644
index 000000000..6da8957cb
--- /dev/null
+++ b/src/lib/nfs/Glue.hxx
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_NFS_GLUE_HXX
+#define MPD_NFS_GLUE_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+class NfsConnection;
+
+void
+nfs_init();
+
+void
+nfs_finish();
+
+gcc_pure
+NfsConnection &
+nfs_get_connection(const char *server, const char *export_name);
+
+#endif
diff --git a/src/lib/nfs/Lease.hxx b/src/lib/nfs/Lease.hxx
new file mode 100644
index 000000000..6f88acf53
--- /dev/null
+++ b/src/lib/nfs/Lease.hxx
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_NFS_LEASE_HXX
+#define MPD_NFS_LEASE_HXX
+
+#include "check.h"
+
+class Error;
+
+class NfsLease {
+public:
+ /**
+ * The #NfsConnection has successfully mounted the server's
+ * export and is ready for regular operation.
+ */
+ virtual void OnNfsConnectionReady() = 0;
+
+ /**
+ * The #NfsConnection has failed to mount the server's export.
+ * This is being called instead of OnNfsConnectionReady().
+ */
+ virtual void OnNfsConnectionFailed(const Error &error) = 0;
+
+ /**
+ * The #NfsConnection has failed after OnNfsConnectionReady()
+ * had been called already.
+ */
+ virtual void OnNfsConnectionDisconnected(const Error &error) = 0;
+};
+
+#endif
diff --git a/src/lib/nfs/Manager.cxx b/src/lib/nfs/Manager.cxx
new file mode 100644
index 000000000..5c236552c
--- /dev/null
+++ b/src/lib/nfs/Manager.cxx
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "Manager.hxx"
+#include "event/Loop.hxx"
+#include "Log.hxx"
+
+void
+NfsManager::ManagedConnection::OnNfsConnectionError(Error &&error)
+{
+ FormatError(error, "NFS error on %s:%s", GetServer(), GetExportName());
+
+ manager.connections.erase(Key(GetServer(), GetExportName()));
+}
+
+NfsConnection &
+NfsManager::GetConnection(const char *server, const char *export_name)
+{
+ assert(server != nullptr);
+ assert(export_name != nullptr);
+ assert(loop.IsInside());
+
+ const std::string key = Key(server, export_name);
+
+#if defined(__GNUC__) && !defined(__clang__) && !GCC_CHECK_VERSION(4,8)
+ /* std::map::emplace() not available; this hack uses the move
+ constructor */
+ auto e = connections.insert(std::make_pair(key,
+ ManagedConnection(*this, loop,
+ server,
+ export_name)));
+#else
+ auto e = connections.emplace(std::piecewise_construct,
+ std::forward_as_tuple(key),
+ std::forward_as_tuple(*this, loop,
+ server,
+ export_name));
+#endif
+ return e.first->second;
+}
diff --git a/src/lib/nfs/Manager.hxx b/src/lib/nfs/Manager.hxx
new file mode 100644
index 000000000..11a779a2a
--- /dev/null
+++ b/src/lib/nfs/Manager.hxx
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_NFS_MANAGER_HXX
+#define MPD_NFS_MANAGER_HXX
+
+#include "check.h"
+#include "Connection.hxx"
+
+#include <string>
+#include <map>
+
+/**
+ * A manager for NFS connections. Handles multiple connections to
+ * multiple NFS servers.
+ */
+class NfsManager {
+ class ManagedConnection final : public NfsConnection {
+ NfsManager &manager;
+
+ public:
+ ManagedConnection(NfsManager &_manager, EventLoop &_loop,
+ const char *_server,
+ const char *_export_name)
+ :NfsConnection(_loop, _server, _export_name),
+ manager(_manager) {}
+
+#if defined(__GNUC__) && !defined(__clang__) && !GCC_CHECK_VERSION(4,8)
+ /* needed due to lack of std::map::emplace() */
+ ManagedConnection(ManagedConnection &&other)
+ :NfsConnection(std::move(other)),
+ manager(other.manager) {}
+#endif
+
+ protected:
+ /* virtual methods from NfsConnection */
+ void OnNfsConnectionError(Error &&error) override;
+ };
+
+ EventLoop &loop;
+
+ /**
+ * Maps server+":"+export_name (see method Key()) to
+ * #ManagedConnection.
+ */
+ std::map<std::string, ManagedConnection> connections;
+
+public:
+ NfsManager(EventLoop &_loop)
+ :loop(_loop) {}
+
+ gcc_pure
+ NfsConnection &GetConnection(const char *server,
+ const char *export_name);
+
+private:
+ gcc_pure
+ static std::string Key(const char *server, const char *export_name) {
+ return std::string(server) + ':' + export_name;
+ }
+};
+
+#endif
diff --git a/src/lib/smbclient/Domain.cxx b/src/lib/smbclient/Domain.cxx
new file mode 100644
index 000000000..00f5ee6c1
--- /dev/null
+++ b/src/lib/smbclient/Domain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain smbclient_domain("smbclient");
diff --git a/src/lib/smbclient/Domain.hxx b/src/lib/smbclient/Domain.hxx
new file mode 100644
index 000000000..3b21c4e60
--- /dev/null
+++ b/src/lib/smbclient/Domain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_SMBCLIENT_DOMAIN_HXX
+#define MPD_SMBCLIENT_DOMAIN_HXX
+
+class Domain;
+
+extern const Domain smbclient_domain;
+
+#endif
diff --git a/src/lib/smbclient/Init.cxx b/src/lib/smbclient/Init.cxx
new file mode 100644
index 000000000..a7f2da4dd
--- /dev/null
+++ b/src/lib/smbclient/Init.cxx
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "Init.hxx"
+#include "Mutex.hxx"
+#include "thread/Mutex.hxx"
+#include "util/Error.hxx"
+
+#include <libsmbclient.h>
+
+#include <string.h>
+
+static void
+mpd_smbc_get_auth_data(gcc_unused const char *srv,
+ gcc_unused const char *shr,
+ char *wg, gcc_unused int wglen,
+ char *un, gcc_unused int unlen,
+ char *pw, gcc_unused int pwlen)
+{
+ // TODO: implement
+ strcpy(wg, "WORKGROUP");
+ strcpy(un, "");
+ strcpy(pw, "");
+}
+
+bool
+SmbclientInit(Error &error)
+{
+ const ScopeLock protect(smbclient_mutex);
+
+ constexpr int debug = 0;
+ if (smbc_init(mpd_smbc_get_auth_data, debug) < 0) {
+ error.SetErrno("smbc_init() failed");
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/lib/smbclient/Init.hxx b/src/lib/smbclient/Init.hxx
new file mode 100644
index 000000000..21014ec8d
--- /dev/null
+++ b/src/lib/smbclient/Init.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_SMBCLIENT_INIT_HXX
+#define MPD_SMBCLIENT_INIT_HXX
+
+#include "check.h"
+
+class Error;
+
+/**
+ * Initialize libsmbclient.
+ */
+bool
+SmbclientInit(Error &error);
+
+#endif
diff --git a/src/lib/smbclient/Mutex.cxx b/src/lib/smbclient/Mutex.cxx
new file mode 100644
index 000000000..4dfc5a9d3
--- /dev/null
+++ b/src/lib/smbclient/Mutex.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "Mutex.hxx"
+#include "thread/Mutex.hxx"
+
+Mutex smbclient_mutex;
diff --git a/src/lib/smbclient/Mutex.hxx b/src/lib/smbclient/Mutex.hxx
new file mode 100644
index 000000000..dc7372e6e
--- /dev/null
+++ b/src/lib/smbclient/Mutex.hxx
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_SMBCLIENT_MUTEX_HXX
+#define MPD_SMBCLIENT_MUTEX_HXX
+
+class Mutex;
+
+/**
+ * Since libsmbclient is not thread-safe, this mutex must be locked
+ * during all libsmbclient function calls.
+ */
+extern Mutex smbclient_mutex;
+
+#endif
diff --git a/src/lib/upnp/Action.hxx b/src/lib/upnp/Action.hxx
new file mode 100644
index 000000000..28c88be92
--- /dev/null
+++ b/src/lib/upnp/Action.hxx
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_UPNP_ACTION_HXX
+#define MPD_UPNP_ACTION_HXX
+
+#include "Compiler.h"
+
+#include <upnp/upnptools.h>
+
+static inline constexpr unsigned
+CountNameValuePairs()
+{
+ return 0;
+}
+
+template<typename... Args>
+static inline constexpr unsigned
+CountNameValuePairs(gcc_unused const char *name, gcc_unused const char *value,
+ Args... args)
+{
+ return 1 + CountNameValuePairs(args...);
+}
+
+/**
+ * A wrapper for UpnpMakeAction() that counts the number of name/value
+ * pairs and adds the nullptr sentinel.
+ */
+template<typename... Args>
+static inline IXML_Document *
+MakeActionHelper(const char *action_name, const char *service_type,
+ Args... args)
+{
+ const unsigned n = CountNameValuePairs(args...);
+ return UpnpMakeAction(action_name, service_type, n,
+ args...,
+ nullptr, nullptr);
+}
+
+#endif
diff --git a/src/lib/upnp/Callback.hxx b/src/lib/upnp/Callback.hxx
new file mode 100644
index 000000000..85daf0a7e
--- /dev/null
+++ b/src/lib/upnp/Callback.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_UPNP_CALLBACK_HXX
+#define MPD_UPNP_CALLBACK_HXX
+
+#include <upnp/upnp.h>
+
+/**
+ * A class that is supposed to be used for libupnp asynchronous
+ * callbacks.
+ */
+class UpnpCallback {
+public:
+ /**
+ * Pass this value as "cookie" pointer to libupnp asynchronous
+ * functions.
+ */
+ void *GetUpnpCookie() {
+ return this;
+ }
+
+ static UpnpCallback &FromUpnpCookie(void *cookie) {
+ return *(UpnpCallback *)cookie;
+ }
+
+ virtual int Invoke(Upnp_EventType et, void *evp) = 0;
+};
+
+#endif
diff --git a/src/lib/upnp/ClientInit.cxx b/src/lib/upnp/ClientInit.cxx
new file mode 100644
index 000000000..77d9cf03d
--- /dev/null
+++ b/src/lib/upnp/ClientInit.cxx
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "ClientInit.hxx"
+#include "Init.hxx"
+#include "Callback.hxx"
+#include "Domain.hxx"
+#include "thread/Mutex.hxx"
+#include "util/Error.hxx"
+
+#include <upnp/upnptools.h>
+
+static Mutex upnp_client_init_mutex;
+static unsigned upnp_client_ref;
+static UpnpClient_Handle upnp_client_handle;
+
+static int
+UpnpClientCallback(Upnp_EventType et, void *evp, void *cookie)
+{
+ if (cookie == nullptr)
+ /* this is the cookie passed to UpnpRegisterClient();
+ but can this ever happen? Will libupnp ever invoke
+ the registered callback without that cookie? */
+ return UPNP_E_SUCCESS;
+
+ UpnpCallback &callback = UpnpCallback::FromUpnpCookie(cookie);
+ return callback.Invoke(et, evp);
+}
+
+static bool
+DoInit(Error &error)
+{
+ auto code = UpnpRegisterClient(UpnpClientCallback, nullptr,
+ &upnp_client_handle);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpRegisterClient() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ return true;
+}
+
+bool
+UpnpClientGlobalInit(UpnpClient_Handle &handle, Error &error)
+{
+ if (!UpnpGlobalInit(error))
+ return false;
+
+ upnp_client_init_mutex.lock();
+ bool success = upnp_client_ref > 0 || DoInit(error);
+ upnp_client_init_mutex.unlock();
+
+ if (success) {
+ ++upnp_client_ref;
+ handle = upnp_client_handle;
+ } else
+ UpnpGlobalFinish();
+
+ return success;
+}
+
+void
+UpnpClientGlobalFinish()
+{
+ upnp_client_init_mutex.lock();
+
+ assert(upnp_client_ref > 0);
+ if (--upnp_client_ref == 0)
+ UpnpUnRegisterClient(upnp_client_handle);
+
+ upnp_client_init_mutex.unlock();
+
+ UpnpGlobalFinish();
+}
diff --git a/src/lib/upnp/ClientInit.hxx b/src/lib/upnp/ClientInit.hxx
new file mode 100644
index 000000000..645e64ca6
--- /dev/null
+++ b/src/lib/upnp/ClientInit.hxx
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_UPNP_CLIENT_INIT_HXX
+#define MPD_UPNP_CLIENT_INIT_HXX
+
+#include "check.h"
+
+#include <upnp/upnp.h>
+
+class Error;
+
+bool
+UpnpClientGlobalInit(UpnpClient_Handle &handle, Error &error);
+
+void
+UpnpClientGlobalFinish();
+
+#endif
diff --git a/src/lib/upnp/ContentDirectoryService.cxx b/src/lib/upnp/ContentDirectoryService.cxx
new file mode 100644
index 000000000..ef8757ec5
--- /dev/null
+++ b/src/lib/upnp/ContentDirectoryService.cxx
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "ContentDirectoryService.hxx"
+#include "Domain.hxx"
+#include "Device.hxx"
+#include "ixmlwrap.hxx"
+#include "Util.hxx"
+#include "Action.hxx"
+#include "util/Error.hxx"
+
+ContentDirectoryService::ContentDirectoryService(const UPnPDevice &device,
+ const UPnPService &service)
+ :m_actionURL(caturl(device.URLBase, service.controlURL)),
+ m_serviceType(service.serviceType),
+ m_deviceId(device.UDN),
+ m_friendlyName(device.friendlyName),
+ m_manufacturer(device.manufacturer),
+ m_modelName(device.modelName),
+ m_rdreqcnt(200)
+{
+ if (!m_modelName.compare("MediaTomb")) {
+ // Readdir by 200 entries is good for most, but MediaTomb likes
+ // them really big. Actually 1000 is better but I don't dare
+ m_rdreqcnt = 500;
+ }
+}
+
+ContentDirectoryService::~ContentDirectoryService()
+{
+ /* this destructor exists here just so it won't get inlined */
+}
+
+bool
+ContentDirectoryService::getSearchCapabilities(UpnpClient_Handle hdl,
+ std::list<std::string> &result,
+ Error &error) const
+{
+ assert(result.empty());
+
+ IXML_Document *request =
+ UpnpMakeAction("GetSearchCapabilities", m_serviceType.c_str(),
+ 0,
+ nullptr, nullptr);
+ if (request == 0) {
+ error.Set(upnp_domain, "UpnpMakeAction() failed");
+ return false;
+ }
+
+ IXML_Document *response;
+ auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
+ m_serviceType.c_str(),
+ 0 /*devUDN*/, request, &response);
+ ixmlDocument_free(request);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSendAction() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ const char *s = ixmlwrap::getFirstElementValue(response, "SearchCaps");
+ if (s == nullptr || *s == 0) {
+ ixmlDocument_free(response);
+ return true;
+ }
+
+ bool success = true;
+ if (!csvToStrings(s, result)) {
+ error.Set(upnp_domain, "Bad response");
+ success = false;
+ }
+
+ ixmlDocument_free(response);
+ return success;
+}
diff --git a/src/lib/upnp/ContentDirectoryService.hxx b/src/lib/upnp/ContentDirectoryService.hxx
new file mode 100644
index 000000000..0b03df2e7
--- /dev/null
+++ b/src/lib/upnp/ContentDirectoryService.hxx
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef _UPNPDIR_HXX_INCLUDED_
+#define _UPNPDIR_HXX_INCLUDED_
+
+#include "Compiler.h"
+
+#include <upnp/upnp.h>
+
+#include <string>
+#include <list>
+
+class Error;
+class UPnPDevice;
+struct UPnPService;
+class UPnPDirContent;
+
+/**
+ * Content Directory Service class.
+ *
+ * This stores identity data from a directory service
+ * and the device it belongs to, and has methods to query
+ * the directory, using libupnp for handling the UPnP protocols.
+ *
+ * Note: m_rdreqcnt: number of entries requested per directory read.
+ * 0 means all entries. The device can still return less entries than
+ * requested, depending on its own limits. In general it's not optimal
+ * becauses it triggers issues, and is sometimes actually slower, e.g. on
+ * a D-Link NAS 327
+ *
+ * The value chosen may affect by the UpnpSetMaxContentLength
+ * (2000*1024) done during initialization, but this should be ample
+ */
+class ContentDirectoryService {
+ std::string m_actionURL;
+ std::string m_serviceType;
+ std::string m_deviceId;
+ std::string m_friendlyName;
+ std::string m_manufacturer;
+ std::string m_modelName;
+
+ int m_rdreqcnt; // Slice size to use when reading
+
+public:
+ /**
+ * Construct by copying data from device and service objects.
+ *
+ * The discovery service does this: use getDirServices()
+ */
+ ContentDirectoryService(const UPnPDevice &device,
+ const UPnPService &service);
+
+ /** An empty one */
+ ContentDirectoryService() = default;
+
+ ~ContentDirectoryService();
+
+ /** Read a container's children list into dirbuf.
+ *
+ * @param objectId the UPnP object Id for the container. Root has Id "0"
+ * @param[out] dirbuf stores the entries we read.
+ */
+ bool readDir(UpnpClient_Handle handle,
+ const char *objectId, UPnPDirContent &dirbuf,
+ Error &error) const;
+
+ bool readDirSlice(UpnpClient_Handle handle,
+ const char *objectId, unsigned offset,
+ unsigned count, UPnPDirContent& dirbuf,
+ unsigned &didread, unsigned &total,
+ Error &error) const;
+
+ /** Search the content directory service.
+ *
+ * @param objectId the UPnP object Id under which the search
+ * should be done. Not all servers actually support this below
+ * root. Root has Id "0"
+ * @param searchstring an UPnP searchcriteria string. Check the
+ * UPnP document: UPnP-av-ContentDirectory-v1-Service-20020625.pdf
+ * section 2.5.5. Maybe we'll provide an easier way some day...
+ * @param[out] dirbuf stores the entries we read.
+ */
+ bool search(UpnpClient_Handle handle,
+ const char *objectId, const char *searchstring,
+ UPnPDirContent &dirbuf,
+ Error &error) const;
+
+ /** Read metadata for a given node.
+ *
+ * @param objectId the UPnP object Id. Root has Id "0"
+ * @param[out] dirbuf stores the entries we read. At most one entry will be
+ * returned.
+ */
+ bool getMetadata(UpnpClient_Handle handle,
+ const char *objectId, UPnPDirContent &dirbuf,
+ Error &error) const;
+
+ /** Retrieve search capabilities
+ *
+ * @param[out] result an empty vector: no search, or a single '*' element:
+ * any tag can be used in a search, or a list of usable tag names.
+ */
+ bool getSearchCapabilities(UpnpClient_Handle handle,
+ std::list<std::string> &result,
+ Error &error) const;
+
+ gcc_pure
+ std::string GetURI() const {
+ return "upnp://" + m_deviceId + "/" + m_serviceType;
+ }
+
+ /** Retrieve the "friendly name" for this server, useful for display. */
+ const char *getFriendlyName() const {
+ return m_friendlyName.c_str();
+ }
+};
+
+#endif /* _UPNPDIR_HXX_INCLUDED_ */
diff --git a/src/lib/upnp/Device.cxx b/src/lib/upnp/Device.cxx
new file mode 100644
index 000000000..26bffd0f0
--- /dev/null
+++ b/src/lib/upnp/Device.cxx
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "Device.hxx"
+#include "Util.hxx"
+#include "lib/expat/ExpatParser.hxx"
+#include "util/Error.hxx"
+
+#include <stdlib.h>
+
+#include <string.h>
+
+UPnPDevice::~UPnPDevice()
+{
+ /* this destructor exists here just so it won't get inlined */
+}
+
+/**
+ * An XML parser which constructs an UPnP device object from the
+ * device descriptor.
+ */
+class UPnPDeviceParser final : public CommonExpatParser {
+ UPnPDevice &m_device;
+
+ std::string *value;
+
+ UPnPService m_tservice;
+
+public:
+ UPnPDeviceParser(UPnPDevice& device)
+ :m_device(device),
+ value(nullptr) {}
+
+protected:
+ virtual void StartElement(const XML_Char *name, const XML_Char **) {
+ value = nullptr;
+
+ switch (name[0]) {
+ case 'c':
+ if (strcmp(name, "controlURL") == 0)
+ value = &m_tservice.controlURL;
+ break;
+ case 'd':
+ if (strcmp(name, "deviceType") == 0)
+ value = &m_device.deviceType;
+ break;
+ case 'f':
+ if (strcmp(name, "friendlyName") == 0)
+ value = &m_device.friendlyName;
+ break;
+ case 'm':
+ if (strcmp(name, "manufacturer") == 0)
+ value = &m_device.manufacturer;
+ else if (strcmp(name, "modelName") == 0)
+ value = &m_device.modelName;
+ break;
+ case 's':
+ if (strcmp(name, "serviceType") == 0)
+ value = &m_tservice.serviceType;
+ break;
+ case 'U':
+ if (strcmp(name, "UDN") == 0)
+ value = &m_device.UDN;
+ else if (strcmp(name, "URLBase") == 0)
+ value = &m_device.URLBase;
+ break;
+ }
+ }
+
+ virtual void EndElement(const XML_Char *name) {
+ if (value != nullptr) {
+ trimstring(*value);
+ value = nullptr;
+ } else if (!strcmp(name, "service")) {
+ m_device.services.emplace_back(std::move(m_tservice));
+ m_tservice.clear();
+ }
+ }
+
+ virtual void CharacterData(const XML_Char *s, int len) {
+ if (value != nullptr)
+ value->append(s, len);
+ }
+};
+
+bool
+UPnPDevice::Parse(const std::string &url, const char *description,
+ Error &error)
+{
+ {
+ UPnPDeviceParser mparser(*this);
+ if (!mparser.Parse(description, strlen(description),
+ true, error))
+ return false;
+ }
+
+ if (URLBase.empty()) {
+ // The standard says that if the URLBase value is empty, we should use
+ // the url the description was retrieved from. However this is
+ // sometimes something like http://host/desc.xml, sometimes something
+ // like http://host/
+
+ if (url.size() < 8) {
+ // ???
+ URLBase = url;
+ } else {
+ auto hostslash = url.find_first_of("/", 7);
+ if (hostslash == std::string::npos || hostslash == url.size()-1) {
+ URLBase = url;
+ } else {
+ URLBase = path_getfather(url);
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/src/lib/upnp/Device.hxx b/src/lib/upnp/Device.hxx
new file mode 100644
index 000000000..dd7ecac2d
--- /dev/null
+++ b/src/lib/upnp/Device.hxx
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef _UPNPDEV_HXX_INCLUDED_
+#define _UPNPDEV_HXX_INCLUDED_
+
+#include <vector>
+#include <string>
+
+class Error;
+
+/**
+ * UPnP Description phase: interpreting the device description which we
+ * downloaded from the URL obtained by the discovery phase.
+ */
+
+/**
+ * Data holder for a UPnP service, parsed from the XML description
+ * downloaded after discovery yielded its URL.
+ */
+struct UPnPService {
+ // e.g. urn:schemas-upnp-org:service:ConnectionManager:1
+ std::string serviceType;
+ std::string controlURL; // e.g.: /upnp/control/cm
+
+ void clear()
+ {
+ serviceType.clear();
+ controlURL.clear();
+ }
+};
+
+/**
+ * Data holder for a UPnP device, parsed from the XML description obtained
+ * during discovery.
+ * A device may include several services. To be of interest to us,
+ * one of them must be a ContentDirectory.
+ */
+class UPnPDevice {
+public:
+ // e.g. urn:schemas-upnp-org:device:MediaServer:1
+ std::string deviceType;
+ // e.g. MediaTomb
+ std::string friendlyName;
+ // Unique device number. This should match the deviceID in the
+ // discovery message. e.g. uuid:a7bdcd12-e6c1-4c7e-b588-3bbc959eda8d
+ std::string UDN;
+ // Base for all relative URLs. e.g. http://192.168.4.4:49152/
+ std::string URLBase;
+ // Manufacturer: e.g. D-Link, PacketVideo ("manufacturer")
+ std::string manufacturer;
+ // Model name: e.g. MediaTomb, DNS-327L ("modelName")
+ std::string modelName;
+ // Services provided by this device.
+ std::vector<UPnPService> services;
+
+ UPnPDevice() = default;
+ UPnPDevice(const UPnPDevice &) = delete;
+ UPnPDevice(UPnPDevice &&) = default;
+ UPnPDevice &operator=(UPnPDevice &&) = default;
+
+ ~UPnPDevice();
+
+ /** Build device from xml description downloaded from discovery
+ * @param url where the description came from
+ * @param description the xml device description
+ */
+ bool Parse(const std::string &url, const char *description,
+ Error &error);
+};
+
+#endif /* _UPNPDEV_HXX_INCLUDED_ */
diff --git a/src/lib/upnp/Discovery.cxx b/src/lib/upnp/Discovery.cxx
new file mode 100644
index 000000000..9ea78c624
--- /dev/null
+++ b/src/lib/upnp/Discovery.cxx
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "Discovery.hxx"
+#include "Domain.hxx"
+#include "ContentDirectoryService.hxx"
+#include "system/Clock.hxx"
+#include "Log.hxx"
+
+#include <upnp/upnptools.h>
+
+#include <string.h>
+
+// The service type string we are looking for.
+static constexpr char ContentDirectorySType[] = "urn:schemas-upnp-org:service:ContentDirectory:1";
+
+// We don't include a version in comparisons, as we are satisfied with
+// version 1
+gcc_pure
+static bool
+isCDService(const char *st)
+{
+ constexpr size_t sz = sizeof(ContentDirectorySType) - 3;
+ return memcmp(ContentDirectorySType, st, sz) == 0;
+}
+
+// The type of device we're asking for in search
+static constexpr char MediaServerDType[] = "urn:schemas-upnp-org:device:MediaServer:1";
+
+gcc_pure
+static bool
+isMSDevice(const char *st)
+{
+ constexpr size_t sz = sizeof(MediaServerDType) - 3;
+ return memcmp(MediaServerDType, st, sz) == 0;
+}
+
+static void
+AnnounceFoundUPnP(UPnPDiscoveryListener &listener, const UPnPDevice &device)
+{
+ for (const auto &service : device.services)
+ if (isCDService(service.serviceType.c_str()))
+ listener.FoundUPnP(ContentDirectoryService(device,
+ service));
+}
+
+static void
+AnnounceLostUPnP(UPnPDiscoveryListener &listener, const UPnPDevice &device)
+{
+ for (const auto &service : device.services)
+ if (isCDService(service.serviceType.c_str()))
+ listener.LostUPnP(ContentDirectoryService(device,
+ service));
+}
+
+inline void
+UPnPDeviceDirectory::LockAdd(ContentDirectoryDescriptor &&d)
+{
+ const ScopeLock protect(mutex);
+
+ for (auto &i : directories) {
+ if (i.id == d.id) {
+ i = std::move(d);
+ return;
+ }
+ }
+
+ directories.emplace_back(std::move(d));
+
+ if (listener != nullptr)
+ AnnounceFoundUPnP(*listener, directories.back().device);
+}
+
+inline void
+UPnPDeviceDirectory::LockRemove(const std::string &id)
+{
+ const ScopeLock protect(mutex);
+
+ for (auto i = directories.begin(), end = directories.end();
+ i != end; ++i) {
+ if (i->id == id) {
+ if (listener != nullptr)
+ AnnounceLostUPnP(*listener, i->device);
+
+ directories.erase(i);
+ break;
+ }
+ }
+}
+
+inline void
+UPnPDeviceDirectory::discoExplorer()
+{
+ for (;;) {
+ DiscoveredTask *tsk = 0;
+ if (!discoveredQueue.take(tsk)) {
+ discoveredQueue.workerExit();
+ return;
+ }
+
+ // Device signals its existence and well-being. Perform the
+ // UPnP "description" phase by downloading and decoding the
+ // description document.
+ char *buf;
+ // LINE_SIZE is defined by libupnp's upnp.h...
+ char contentType[LINE_SIZE];
+ int code = UpnpDownloadUrlItem(tsk->url.c_str(), &buf, contentType);
+ if (code != UPNP_E_SUCCESS) {
+ continue;
+ }
+
+ // Update or insert the device
+ ContentDirectoryDescriptor d(std::move(tsk->deviceId),
+ MonotonicClockS(), tsk->expires);
+
+ {
+ Error error2;
+ bool success = d.Parse(tsk->url, buf, error2);
+ free(buf);
+ if (!success) {
+ delete tsk;
+ LogError(error2);
+ continue;
+ }
+ }
+
+ LockAdd(std::move(d));
+ delete tsk;
+ }
+}
+
+void *
+UPnPDeviceDirectory::discoExplorer(void *ctx)
+{
+ UPnPDeviceDirectory &directory = *(UPnPDeviceDirectory *)ctx;
+ directory.discoExplorer();
+ return (void*)1;
+}
+
+inline int
+UPnPDeviceDirectory::OnAlive(Upnp_Discovery *disco)
+{
+ if (isMSDevice(disco->DeviceType) ||
+ isCDService(disco->ServiceType)) {
+ DiscoveredTask *tp = new DiscoveredTask(disco);
+ if (discoveredQueue.put(tp))
+ return UPNP_E_FINISH;
+ }
+
+ return UPNP_E_SUCCESS;
+}
+
+inline int
+UPnPDeviceDirectory::OnByeBye(Upnp_Discovery *disco)
+{
+ if (isMSDevice(disco->DeviceType) ||
+ isCDService(disco->ServiceType)) {
+ // Device signals it is going off.
+ LockRemove(disco->DeviceId);
+ }
+
+ return UPNP_E_SUCCESS;
+}
+
+// This gets called for all libupnp asynchronous events, in a libupnp
+// thread context.
+// Example: ContentDirectories appearing and disappearing from the network
+// We queue a task for our worker thread(s)
+int
+UPnPDeviceDirectory::Invoke(Upnp_EventType et, void *evp)
+{
+ switch (et) {
+ case UPNP_DISCOVERY_SEARCH_RESULT:
+ case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
+ {
+ Upnp_Discovery *disco = (Upnp_Discovery *)evp;
+ return OnAlive(disco);
+ }
+
+ case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
+ {
+ Upnp_Discovery *disco = (Upnp_Discovery *)evp;
+ return OnByeBye(disco);
+ }
+
+ default:
+ // Ignore other events for now
+ break;
+ }
+
+ return UPNP_E_SUCCESS;
+}
+
+bool
+UPnPDeviceDirectory::expireDevices(Error &error)
+{
+ const ScopeLock protect(mutex);
+ const unsigned now = MonotonicClockS();
+ bool didsomething = false;
+
+ for (auto it = directories.begin();
+ it != directories.end();) {
+ if (now > it->expires) {
+ it = directories.erase(it);
+ didsomething = true;
+ } else {
+ it++;
+ }
+ }
+
+ if (didsomething)
+ return search(error);
+
+ return true;
+}
+
+UPnPDeviceDirectory::UPnPDeviceDirectory(UpnpClient_Handle _handle,
+ UPnPDiscoveryListener *_listener)
+ :handle(_handle),
+ listener(_listener),
+ discoveredQueue("DiscoveredQueue"),
+ m_searchTimeout(2), m_lastSearch(0)
+{
+}
+
+UPnPDeviceDirectory::~UPnPDeviceDirectory()
+{
+ /* this destructor exists here just so it won't get inlined */
+}
+
+bool
+UPnPDeviceDirectory::Start(Error &error)
+{
+ if (!discoveredQueue.start(1, discoExplorer, this)) {
+ error.Set(upnp_domain, "Discover work queue start failed");
+ return false;
+ }
+
+ return search(error);
+}
+
+bool
+UPnPDeviceDirectory::search(Error &error)
+{
+ const unsigned now = MonotonicClockS();
+ if (now - m_lastSearch < 10)
+ return true;
+ m_lastSearch = now;
+
+ // We search both for device and service just in case.
+ int code = UpnpSearchAsync(handle, m_searchTimeout,
+ ContentDirectorySType, GetUpnpCookie());
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSearchAsync() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ code = UpnpSearchAsync(handle, m_searchTimeout,
+ MediaServerDType, GetUpnpCookie());
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSearchAsync() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ return true;
+}
+
+bool
+UPnPDeviceDirectory::getDirServices(std::vector<ContentDirectoryService> &out,
+ Error &error)
+{
+ // Has locking, do it before our own lock
+ if (!expireDevices(error))
+ return false;
+
+ const ScopeLock protect(mutex);
+
+ for (auto dit = directories.begin();
+ dit != directories.end(); dit++) {
+ for (const auto &service : dit->device.services) {
+ if (isCDService(service.serviceType.c_str())) {
+ out.emplace_back(dit->device, service);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool
+UPnPDeviceDirectory::getServer(const char *friendlyName,
+ ContentDirectoryService &server,
+ Error &error)
+{
+ // Has locking, do it before our own lock
+ if (!expireDevices(error))
+ return false;
+
+ const ScopeLock protect(mutex);
+
+ for (const auto &i : directories) {
+ const auto &device = i.device;
+
+ if (device.friendlyName != friendlyName)
+ continue;
+
+ for (const auto &service : device.services) {
+ if (isCDService(service.serviceType.c_str())) {
+ server = ContentDirectoryService(device,
+ service);
+ return true;
+ }
+ }
+ }
+
+ error.Set(upnp_domain, "Server not found");
+ return false;
+}
diff --git a/src/lib/upnp/Discovery.hxx b/src/lib/upnp/Discovery.hxx
new file mode 100644
index 000000000..af07daf61
--- /dev/null
+++ b/src/lib/upnp/Discovery.hxx
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef _UPNPPDISC_H_X_INCLUDED_
+#define _UPNPPDISC_H_X_INCLUDED_
+
+#include "Callback.hxx"
+#include "Device.hxx"
+#include "WorkQueue.hxx"
+#include "thread/Mutex.hxx"
+#include "util/Error.hxx"
+
+#include <upnp/upnp.h>
+
+#include <list>
+#include <vector>
+#include <string>
+
+class ContentDirectoryService;
+
+class UPnPDiscoveryListener {
+public:
+ virtual void FoundUPnP(const ContentDirectoryService &service) = 0;
+ virtual void LostUPnP(const ContentDirectoryService &service) = 0;
+};
+
+/**
+ * Manage UPnP discovery and maintain a directory of active devices. Singleton.
+ *
+ * We are only interested in MediaServers with a ContentDirectory service
+ * for now, but this could be made more general, by removing the filtering.
+ */
+class UPnPDeviceDirectory final : UpnpCallback {
+ /**
+ * Each appropriate discovery event (executing in a libupnp thread
+ * context) queues the following task object for processing by the
+ * discovery thread.
+ */
+ struct DiscoveredTask {
+ std::string url;
+ std::string deviceId;
+ unsigned expires; // Seconds valid
+
+ DiscoveredTask(const Upnp_Discovery *disco)
+ :url(disco->Location),
+ deviceId(disco->DeviceId),
+ expires(disco->Expires) {}
+ };
+
+ /**
+ * Descriptor for one device having a Content Directory
+ * service found on the network.
+ */
+ class ContentDirectoryDescriptor {
+ public:
+ std::string id;
+
+ UPnPDevice device;
+
+ /**
+ * The MonotonicClockS() time stamp when this device
+ * expires.
+ */
+ unsigned expires;
+
+ ContentDirectoryDescriptor() = default;
+
+ ContentDirectoryDescriptor(std::string &&_id,
+ unsigned last, int exp)
+ :id(std::move(_id)), expires(last + exp + 20) {}
+
+ bool Parse(const std::string &url, const char *description,
+ Error &_error) {
+ return device.Parse(url, description, _error);
+ }
+ };
+
+ const UpnpClient_Handle handle;
+ UPnPDiscoveryListener *const listener;
+
+ Mutex mutex;
+ std::list<ContentDirectoryDescriptor> directories;
+ WorkQueue<DiscoveredTask *> discoveredQueue;
+
+ /**
+ * The UPnP device search timeout, which should actually be
+ * called delay because it's the base of a random delay that
+ * the devices apply to avoid responding all at the same time.
+ */
+ int m_searchTimeout;
+
+ /**
+ * The MonotonicClockS() time stamp of the last search.
+ */
+ unsigned m_lastSearch;
+
+public:
+ UPnPDeviceDirectory(UpnpClient_Handle _handle,
+ UPnPDiscoveryListener *_listener=nullptr);
+ ~UPnPDeviceDirectory();
+
+ UPnPDeviceDirectory(const UPnPDeviceDirectory &) = delete;
+ UPnPDeviceDirectory& operator=(const UPnPDeviceDirectory &) = delete;
+
+ bool Start(Error &error);
+
+ /** Retrieve the directory services currently seen on the network */
+ bool getDirServices(std::vector<ContentDirectoryService> &, Error &);
+
+ /**
+ * Get server by friendly name.
+ */
+ bool getServer(const char *friendlyName,
+ ContentDirectoryService &server,
+ Error &error);
+
+private:
+ bool search(Error &error);
+
+ /**
+ * Look at the devices and get rid of those which have not
+ * been seen for too long. We do this when listing the top
+ * directory.
+ */
+ bool expireDevices(Error &error);
+
+ void LockAdd(ContentDirectoryDescriptor &&d);
+ void LockRemove(const std::string &id);
+
+ /**
+ * Worker routine for the discovery queue. Get messages about
+ * devices appearing and disappearing, and update the
+ * directory pool accordingly.
+ */
+ static void *discoExplorer(void *);
+ void discoExplorer();
+
+ int OnAlive(Upnp_Discovery *disco);
+ int OnByeBye(Upnp_Discovery *disco);
+ int cluCallBack(Upnp_EventType et, void *evp);
+
+ /* virtual methods from class UpnpCallback */
+ virtual int Invoke(Upnp_EventType et, void *evp) override;
+};
+
+
+#endif /* _UPNPPDISC_H_X_INCLUDED_ */
diff --git a/src/lib/upnp/Domain.cxx b/src/lib/upnp/Domain.cxx
new file mode 100644
index 000000000..010d4c7c2
--- /dev/null
+++ b/src/lib/upnp/Domain.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain upnp_domain("upnp");
diff --git a/src/lib/upnp/Domain.hxx b/src/lib/upnp/Domain.hxx
new file mode 100644
index 000000000..ec01ef735
--- /dev/null
+++ b/src/lib/upnp/Domain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_UPNP_DOMAIN_HXX
+#define MPD_UPNP_DOMAIN_HXX
+
+class Domain;
+
+extern const Domain upnp_domain;
+
+#endif
diff --git a/src/lib/upnp/Init.cxx b/src/lib/upnp/Init.cxx
new file mode 100644
index 000000000..4fc606de9
--- /dev/null
+++ b/src/lib/upnp/Init.cxx
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "config.h"
+#include "Init.hxx"
+#include "Domain.hxx"
+#include "thread/Mutex.hxx"
+#include "util/Error.hxx"
+
+#include <upnp/upnp.h>
+#include <upnp/upnptools.h>
+#include <upnp/ixml.h>
+
+static Mutex upnp_init_mutex;
+static unsigned upnp_ref;
+
+static bool
+DoInit(Error &error)
+{
+ auto code = UpnpInit(0, 0);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpInit() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ UpnpSetMaxContentLength(2000*1024);
+
+ // Servers sometimes make error (e.g.: minidlna returns bad utf-8)
+ ixmlRelaxParser(1);
+
+ return true;
+}
+
+bool
+UpnpGlobalInit(Error &error)
+{
+ const ScopeLock protect(upnp_init_mutex);
+
+ if (upnp_ref == 0 && !DoInit(error))
+ return false;
+
+ ++upnp_ref;
+ return true;
+}
+
+void
+UpnpGlobalFinish()
+{
+ const ScopeLock protect(upnp_init_mutex);
+
+ assert(upnp_ref > 0);
+
+ if (--upnp_ref == 0)
+ UpnpFinish();
+}
diff --git a/src/lib/upnp/Init.hxx b/src/lib/upnp/Init.hxx
new file mode 100644
index 000000000..b23f8e2ab
--- /dev/null
+++ b/src/lib/upnp/Init.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_UPNP_INIT_HXX
+#define MPD_UPNP_INIT_HXX
+
+#include "check.h"
+
+class Error;
+
+bool
+UpnpGlobalInit(Error &error);
+
+void
+UpnpGlobalFinish();
+
+#endif
diff --git a/src/lib/upnp/Util.cxx b/src/lib/upnp/Util.cxx
new file mode 100644
index 000000000..cf34a47d3
--- /dev/null
+++ b/src/lib/upnp/Util.cxx
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#include "Util.hxx"
+
+#include <upnp/ixml.h>
+
+#include <assert.h>
+
+/** Get rid of white space at both ends */
+void
+trimstring(std::string &s, const char *ws)
+{
+ auto pos = s.find_first_not_of(ws);
+ if (pos == std::string::npos) {
+ s.clear();
+ return;
+ }
+ s.replace(0, pos, std::string());
+
+ pos = s.find_last_not_of(ws);
+ if (pos != std::string::npos && pos != s.length()-1)
+ s.replace(pos + 1, std::string::npos, std::string());
+}
+
+std::string
+caturl(const std::string &s1, const std::string &s2)
+{
+ if (s2.front() == '/') {
+ /* absolute path: replace the whole URI path in s1 */
+
+ auto i = s1.find("://");
+ if (i == s1.npos)
+ /* no scheme: override s1 completely */
+ return s2;
+
+ /* find the first slash after the host part */
+ i = s1.find('/', i + 3);
+ if (i == s1.npos)
+ /* there's no URI path - simply append s2 */
+ i = s1.length();
+
+ return s1.substr(0, i) + s2;
+ }
+
+ std::string out(s1);
+ if (out.back() != '/')
+ out.push_back('/');
+
+ out += s2;
+ return out;
+}
+
+static void
+path_catslash(std::string &s)
+{
+ if (s.empty() || s.back() != '/')
+ s += '/';
+}
+
+std::string
+path_getfather(const std::string &s)
+{
+ std::string father = s;
+
+ // ??
+ if (father.empty())
+ return "./";
+
+ if (father.back() == '/') {
+ // Input ends with /. Strip it, handle special case for root
+ if (father.length() == 1)
+ return father;
+ father.erase(father.length()-1);
+ }
+
+ auto slp = father.rfind('/');
+ if (slp == std::string::npos)
+ return "./";
+
+ father.erase(slp);
+ path_catslash(father);
+ return father;
+}
+
+std::list<std::string>
+stringToTokens(const std::string &str,
+ const char *delims, bool skipinit)
+{
+ std::list<std::string> tokens;
+
+ std::string::size_type startPos = 0;
+
+ // Skip initial delims, return empty if this eats all.
+ if (skipinit &&
+ (startPos = str.find_first_not_of(delims, 0)) == std::string::npos)
+ return tokens;
+
+ while (startPos < str.size()) {
+ // Find next delimiter or end of string (end of token)
+ auto pos = str.find_first_of(delims, startPos);
+
+ // Add token to the vector and adjust start
+ if (pos == std::string::npos) {
+ tokens.emplace_back(str, startPos);
+ break;
+ } else if (pos == startPos) {
+ // Dont' push empty tokens after first
+ if (tokens.empty())
+ tokens.emplace_back();
+ startPos = ++pos;
+ } else {
+ tokens.emplace_back(str, startPos, pos - startPos);
+ startPos = ++pos;
+ }
+ }
+
+ return tokens;
+}
+
+template <class T>
+bool
+csvToStrings(const char *s, T &tokens)
+{
+ assert(tokens.empty());
+
+ std::string current;
+
+ while (true) {
+ char ch = *s++;
+ if (ch == 0) {
+ tokens.emplace_back(std::move(current));
+ return true;
+ }
+
+ if (ch == '\\') {
+ ch = *s++;
+ if (ch == 0)
+ return false;
+ } else if (ch == ',') {
+ tokens.emplace_back(std::move(current));
+ current.clear();
+ continue;
+ }
+
+ current.push_back(ch);
+ }
+}
+
+template bool csvToStrings<std::list<std::string>>(const char *, std::list<std::string> &);
diff --git a/src/lib/upnp/Util.hxx b/src/lib/upnp/Util.hxx
new file mode 100644
index 000000000..58e382faa
--- /dev/null
+++ b/src/lib/upnp/Util.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_UPNP_UTIL_HXX
+#define MPD_UPNP_UTIL_HXX
+
+#include "Compiler.h"
+
+#include <string>
+#include <list>
+
+std::string
+caturl(const std::string& s1, const std::string& s2);
+
+void
+trimstring(std::string &s, const char *ws = " \t\n");
+
+std::string
+path_getfather(const std::string &s);
+
+gcc_pure
+std::list<std::string>
+stringToTokens(const std::string &str,
+ const char *delims = "/", bool skipinit = true);
+
+template <class T>
+bool
+csvToStrings(const char *s, T &tokens);
+
+#endif /* _UPNPP_H_X_INCLUDED_ */
diff --git a/src/lib/upnp/WorkQueue.hxx b/src/lib/upnp/WorkQueue.hxx
new file mode 100644
index 000000000..fe8ce53f9
--- /dev/null
+++ b/src/lib/upnp/WorkQueue.hxx
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef _WORKQUEUE_H_INCLUDED_
+#define _WORKQUEUE_H_INCLUDED_
+
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+#include <assert.h>
+#include <pthread.h>
+
+#include <string>
+#include <queue>
+
+#define LOGINFO(X)
+#define LOGERR(X)
+
+/**
+ * A WorkQueue manages the synchronisation around a queue of work items,
+ * where a number of client threads queue tasks and a number of worker
+ * threads take and execute them. The goal is to introduce some level
+ * of parallelism between the successive steps of a previously single
+ * threaded pipeline. For example data extraction / data preparation / index
+ * update, but this could have other uses.
+ *
+ * There is no individual task status return. In case of fatal error,
+ * the client or worker sets an end condition on the queue. A second
+ * queue could conceivably be used for returning individual task
+ * status.
+ */
+template <class T>
+class WorkQueue {
+ // Configuration
+ const std::string name;
+
+ // Status
+ // Worker threads having called exit
+ unsigned n_workers_exited;
+ bool ok;
+
+ unsigned n_threads;
+ pthread_t *threads;
+
+ // Synchronization
+ std::queue<T> queue;
+ Cond client_cond;
+ Cond worker_cond;
+ Mutex mutex;
+
+public:
+ /** Create a WorkQueue
+ * @param name for message printing
+ * @param hi number of tasks on queue before clients blocks. Default 0
+ * meaning no limit. hi == -1 means that the queue is disabled.
+ * @param lo minimum count of tasks before worker starts. Default 1.
+ */
+ WorkQueue(const char *_name)
+ :name(_name),
+ n_workers_exited(0),
+ ok(false),
+ n_threads(0), threads(nullptr)
+ {
+ }
+
+ ~WorkQueue() {
+ setTerminateAndWait();
+ }
+
+ /** Start the worker threads.
+ *
+ * @param nworkers number of threads copies to start.
+ * @param start_routine thread function. It should loop
+ * taking (QueueWorker::take()) and executing tasks.
+ * @param arg initial parameter to thread function.
+ * @return true if ok.
+ */
+ bool start(unsigned nworkers, void *(*workproc)(void *), void *arg)
+ {
+ const ScopeLock protect(mutex);
+
+ assert(nworkers > 0);
+ assert(!ok);
+ assert(n_threads == 0);
+ assert(threads == nullptr);
+
+ n_threads = nworkers;
+ threads = new pthread_t[n_threads];
+
+ for (unsigned i = 0; i < nworkers; i++) {
+ int err;
+ if ((err = pthread_create(&threads[i], 0, workproc, arg))) {
+ LOGERR(("WorkQueue:%s: pthread_create failed, err %d\n",
+ name.c_str(), err));
+ return false;
+ }
+ }
+
+ ok = true;
+ return true;
+ }
+
+ /** Add item to work queue, called from client.
+ *
+ * Sleeps if there are already too many.
+ */
+ template<typename U>
+ bool put(U &&u)
+ {
+ const ScopeLock protect(mutex);
+
+ queue.emplace(std::forward<U>(u));
+
+ // Just wake one worker, there is only one new task.
+ worker_cond.signal();
+
+ return true;
+ }
+
+
+ /** Tell the workers to exit, and wait for them.
+ */
+ void setTerminateAndWait()
+ {
+ const ScopeLock protect(mutex);
+
+ // Wait for all worker threads to have called workerExit()
+ ok = false;
+ while (n_workers_exited < n_threads) {
+ worker_cond.broadcast();
+ client_cond.wait(mutex);
+ }
+
+ // Perform the thread joins and compute overall status
+ // Workers return (void*)1 if ok
+ for (unsigned i = 0; i < n_threads; ++i) {
+ void *status;
+ pthread_join(threads[i], &status);
+ }
+
+ delete[] threads;
+ threads = nullptr;
+ n_threads = 0;
+
+ // Reset to start state.
+ n_workers_exited = 0;
+ }
+
+ /** Take task from queue. Called from worker.
+ *
+ * Sleeps if there are not enough. Signal if we go to sleep on empty
+ * queue: client may be waiting for our going idle.
+ */
+ bool take(T &tp)
+ {
+ const ScopeLock protect(mutex);
+
+ if (!ok)
+ return false;
+
+ while (queue.empty()) {
+ worker_cond.wait(mutex);
+ if (!ok)
+ return false;
+ }
+
+ tp = std::move(queue.front());
+ queue.pop();
+ return true;
+ }
+
+ /** Advertise exit and abort queue. Called from worker
+ *
+ * This would happen after an unrecoverable error, or when
+ * the queue is terminated by the client. Workers never exit normally,
+ * except when the queue is shut down (at which point ok is set to
+ * false by the shutdown code anyway). The thread must return/exit
+ * immediately after calling this.
+ */
+ void workerExit()
+ {
+ const ScopeLock protect(mutex);
+
+ n_workers_exited++;
+ ok = false;
+ client_cond.broadcast();
+ }
+};
+
+#endif /* _WORKQUEUE_H_INCLUDED_ */
diff --git a/src/lib/upnp/ixmlwrap.cxx b/src/lib/upnp/ixmlwrap.cxx
new file mode 100644
index 000000000..6a2829cf9
--- /dev/null
+++ b/src/lib/upnp/ixmlwrap.cxx
@@ -0,0 +1,44 @@
+/* Copyright (C) 2013 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 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.
+ */
+
+#include "ixmlwrap.hxx"
+
+namespace ixmlwrap {
+
+const char *
+getFirstElementValue(IXML_Document *doc, const char *name)
+{
+ const char *ret = nullptr;
+ IXML_NodeList *nodes =
+ ixmlDocument_getElementsByTagName(doc, name);
+
+ if (nodes) {
+ IXML_Node *first = ixmlNodeList_item(nodes, 0);
+ if (first) {
+ IXML_Node *dnode = ixmlNode_getFirstChild(first);
+ if (dnode) {
+ ret = ixmlNode_getNodeValue(dnode);
+ }
+ }
+
+ ixmlNodeList_free(nodes);
+ }
+
+ return ret;
+}
+
+}
diff --git a/src/lib/upnp/ixmlwrap.hxx b/src/lib/upnp/ixmlwrap.hxx
new file mode 100644
index 000000000..0d519a323
--- /dev/null
+++ b/src/lib/upnp/ixmlwrap.hxx
@@ -0,0 +1,35 @@
+/* Copyright (C) 2013 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 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.
+ */
+#ifndef _IXMLWRAP_H_INCLUDED_
+#define _IXMLWRAP_H_INCLUDED_
+
+#include <upnp/ixml.h>
+
+#include <string>
+
+namespace ixmlwrap {
+ /**
+ * Retrieve the text content for the first element of given
+ * name. Returns nullptr if the element does not
+ * contain a text node
+ */
+ const char *getFirstElementValue(IXML_Document *doc,
+ const char *name);
+
+};
+
+#endif /* _IXMLWRAP_H_INCLUDED_ */