summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am54
-rw-r--r--NEWS1
-rw-r--r--configure.ac21
-rw-r--r--src/CommandLine.cxx14
-rw-r--r--src/Idle.cxx1
-rw-r--r--src/Idle.hxx3
-rw-r--r--src/Instance.cxx16
-rw-r--r--src/Instance.hxx22
-rw-r--r--src/Main.cxx30
-rw-r--r--src/command/AllCommands.cxx9
-rw-r--r--src/command/NeighborCommands.cxx54
-rw-r--r--src/command/NeighborCommands.hxx35
-rw-r--r--src/config/ConfigOption.hxx1
-rw-r--r--src/config/ConfigTemplates.cxx1
-rw-r--r--src/neighbor/Explorer.hxx71
-rw-r--r--src/neighbor/Glue.cxx111
-rw-r--r--src/neighbor/Glue.hxx76
-rw-r--r--src/neighbor/Info.hxx30
-rw-r--r--src/neighbor/Listener.hxx36
-rw-r--r--src/neighbor/NeighborPlugin.hxx40
-rw-r--r--src/neighbor/Registry.cxx42
-rw-r--r--src/neighbor/Registry.hxx37
-rw-r--r--src/neighbor/plugins/SmbclientNeighborPlugin.cxx285
-rw-r--r--src/neighbor/plugins/SmbclientNeighborPlugin.hxx27
-rw-r--r--test/.gitignore1
-rw-r--r--test/run_neighbor_explorer.cxx85
26 files changed, 1101 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am
index 6bb3c9094..ee4116211 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -17,6 +17,7 @@ noinst_LIBRARIES = \
libtag.a \
libinput.a \
libfs.a \
+ libneighbor.a \
libdb_plugins.a \
libplaylist_plugins.a \
libdecoder_plugins.a \
@@ -30,6 +31,7 @@ src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
$(LIBWRAP_CFLAGS) \
$(SQLITE_CFLAGS)
src_mpd_LDADD = \
+ $(NEIGHBOR_LIBS) \
$(DB_LIBS) \
$(PLAYLIST_LIBS) \
$(AVAHI_LIBS) \
@@ -398,6 +400,37 @@ libfs_a_SOURCES = \
src/fs/StandardDirectory.cxx src/fs/StandardDirectory.hxx \
src/fs/DirectoryReader.hxx
+# neighbor plugins
+
+if ENABLE_NEIGHBOR_PLUGINS
+
+src_mpd_SOURCES += \
+ src/command/NeighborCommands.cxx \
+ src/command/NeighborCommands.hxx
+
+libneighbor_a_SOURCES = \
+ src/neighbor/Registry.cxx src/neighbor/Registry.hxx \
+ src/neighbor/Glue.cxx src/neighbor/Glue.hxx \
+ src/neighbor/Info.hxx \
+ src/neighbor/Listener.hxx \
+ src/neighbor/Explorer.hxx \
+ src/neighbor/NeighborPlugin.hxx
+
+libneighbor_a_CPPFLAGS = $(AM_CPPFLAGS) \
+ $(SMBCLIENT_CFLAGS)
+
+if ENABLE_SMBCLIENT
+libneighbor_a_SOURCES += \
+ src/lib/smbclient/Init.cxx src/lib/smbclient/Init.hxx \
+ src/neighbor/plugins/SmbclientNeighborPlugin.cxx src/neighbor/plugins/SmbclientNeighborPlugin.hxx
+endif
+
+NEIGHBOR_LIBS = \
+ $(SMBCLIENT_LIBS) \
+ libneighbor.a
+
+endif
+
# database plugins
libdb_plugins_a_SOURCES = \
@@ -1199,6 +1232,10 @@ noinst_PROGRAMS = \
test/run_normalize \
test/software_volume
+if ENABLE_NEIGHBOR_PLUGINS
+noinst_PROGRAMS += test/run_neighbor_explorer
+endif
+
if HAVE_AVAHI
noinst_PROGRAMS += test/run_avahi
endif
@@ -1278,6 +1315,23 @@ test_run_input_SOURCES = test/run_input.cxx \
src/IOThread.cxx \
src/TagSave.cxx
+if ENABLE_NEIGHBOR_PLUGINS
+
+test_run_neighbor_explorer_SOURCES = \
+ src/Log.cxx src/LogBackend.cxx \
+ test/run_neighbor_explorer.cxx
+test_run_neighbor_explorer_LDADD = \
+ $(GLIB_LIBS) \
+ $(NEIGHBOR_LIBS) \
+ libconf.a \
+ libevent.a \
+ libfs.a \
+ libsystem.a \
+ libthread.a \
+ libutil.a
+
+endif
+
if ENABLE_ARCHIVE
test_visit_archive_LDADD = \
diff --git a/NEWS b/NEWS
index 71ab008b3..2c22aae99 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,7 @@ ver 0.19 (not yet released)
* protocol
- new commands "addtagid", "cleartagid"
- "lsinfo" and "readcomments" allowed for remote files
+ - "listneighbors" lists file servers on the local network
* database
- proxy: forward "idle" events
- proxy: copy "Last-Modified" from remote directories
diff --git a/configure.ac b/configure.ac
index 1bed0727d..19ead794b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -885,6 +885,27 @@ fi
AM_CONDITIONAL(ENABLE_MMS, test x$enable_mms = xyes)
dnl ---------------------------------------------------------------------------
+dnl Neighbor Plugins
+dnl ---------------------------------------------------------------------------
+
+AC_ARG_ENABLE(neighbor-plugins,
+ AS_HELP_STRING([--enable-neighbor-plugins],
+ [enable support for neighbor discovery (default: auto)]),,
+ [enable_neighbor_plugins=auto])
+
+if test x$enable_neighbor_plugins = xauto; then
+ if test x$enable_smbclient = xyes; then
+ enable_neighbor_plugins=yes
+ fi
+fi
+
+if test x$enable_neighbor_plugins = xyes; then
+ AC_DEFINE(ENABLE_NEIGHBOR_PLUGINS, 1,
+ [Define to enable support for neighbor discovery])
+fi
+AM_CONDITIONAL(ENABLE_NEIGHBOR_PLUGINS, test x$enable_neighbor_plugins = xyes)
+
+dnl ---------------------------------------------------------------------------
dnl Archive Plugins
dnl ---------------------------------------------------------------------------
diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx
index c5adc4153..19bc9d4bb 100644
--- a/src/CommandLine.cxx
+++ b/src/CommandLine.cxx
@@ -42,6 +42,11 @@
#include "util/OptionDef.hxx"
#include "util/OptionParser.hxx"
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+#include "neighbor/Registry.hxx"
+#include "neighbor/NeighborPlugin.hxx"
+#endif
+
#ifdef ENABLE_ENCODER
#include "encoder/EncoderList.hxx"
#include "encoder/EncoderPlugin.hxx"
@@ -104,6 +109,13 @@ static void version(void)
for (auto i = database_plugins; *i != nullptr; ++i)
printf(" %s", (*i)->name);
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ puts("\n\n"
+ "Neighbor plugins:");
+ for (auto i = neighbor_plugins; *i != nullptr; ++i)
+ printf(" %s", (*i)->name);
+#endif
+
puts("\n\n"
"Decoders plugins:");
@@ -148,7 +160,7 @@ static void version(void)
#endif
puts("\n"
- "input/Input plugins:");
+ "Input plugins:");
input_plugins_for_each(plugin)
printf(" %s", plugin->name);
diff --git a/src/Idle.cxx b/src/Idle.cxx
index bf08caa99..ed16bbecb 100644
--- a/src/Idle.cxx
+++ b/src/Idle.cxx
@@ -44,6 +44,7 @@ static const char *const idle_names[] = {
"update",
"subscription",
"message",
+ "neighbor",
nullptr
};
diff --git a/src/Idle.hxx b/src/Idle.hxx
index a0fbad32c..7fc79d10a 100644
--- a/src/Idle.hxx
+++ b/src/Idle.hxx
@@ -59,6 +59,9 @@ static constexpr unsigned IDLE_SUBSCRIPTION = 0x200;
/** a message on the subscribed channel was received */
static constexpr unsigned IDLE_MESSAGE = 0x400;
+/** a neighbor was found or lost */
+static constexpr unsigned IDLE_NEIGHBOR = 0x800;
+
/**
* Adds idle flag (with bitwise "or") and queues notifications to all
* clients.
diff --git a/src/Instance.cxx b/src/Instance.cxx
index 5b7c2de2d..16000afb3 100644
--- a/src/Instance.cxx
+++ b/src/Instance.cxx
@@ -54,3 +54,19 @@ Instance::OnDatabaseModified()
{
DatabaseModified();
}
+
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+
+void
+Instance::FoundNeighbor(gcc_unused const NeighborInfo &info)
+{
+ idle_add(IDLE_NEIGHBOR);
+}
+
+void
+Instance::LostNeighbor(gcc_unused const NeighborInfo &info)
+{
+ idle_add(IDLE_NEIGHBOR);
+}
+
+#endif
diff --git a/src/Instance.hxx b/src/Instance.hxx
index ca7bb5197..a14719839 100644
--- a/src/Instance.hxx
+++ b/src/Instance.hxx
@@ -24,10 +24,24 @@
#include "db/DatabaseListener.hxx"
#include "Compiler.h"
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+#include "neighbor/Listener.hxx"
+class NeighborGlue;
+#endif
+
class ClientList;
struct Partition;
-struct Instance final : public DatabaseListener {
+struct Instance final
+ : public DatabaseListener
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ , public NeighborListener
+#endif
+{
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ NeighborGlue *neighbors;
+#endif
+
ClientList *client_list;
Partition *partition;
@@ -53,6 +67,12 @@ struct Instance final : public DatabaseListener {
private:
virtual void OnDatabaseModified();
+
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ /* virtual methods from class NeighborListener */
+ virtual void FoundNeighbor(const NeighborInfo &info) override;
+ virtual void LostNeighbor(const NeighborInfo &info) override;
+#endif
};
#endif
diff --git a/src/Main.cxx b/src/Main.cxx
index fc7efd036..f790ec574 100644
--- a/src/Main.cxx
+++ b/src/Main.cxx
@@ -67,6 +67,10 @@
#include "config/ConfigOption.hxx"
#include "Stats.hxx"
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+#include "neighbor/Glue.hxx"
+#endif
+
#ifdef ENABLE_INOTIFY
#include "db/update/InotifyUpdate.hxx"
#endif
@@ -394,6 +398,19 @@ int mpd_main(int argc, char *argv[])
instance = new Instance();
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ instance->neighbors = new NeighborGlue();
+ if (!instance->neighbors->Init(io_thread_get(), *instance, error)) {
+ LogError(error);
+ return EXIT_FAILURE;
+ }
+
+ if (instance->neighbors->IsEmpty()) {
+ delete instance->neighbors;
+ instance->neighbors = nullptr;
+ }
+#endif
+
const unsigned max_clients = config_get_positive(CONF_MAX_CONN, 10);
instance->client_list = new ClientList(max_clients);
@@ -460,6 +477,12 @@ int mpd_main(int argc, char *argv[])
io_thread_start();
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ if (instance->neighbors != nullptr &&
+ !instance->neighbors->Open(error))
+ FatalError(error);
+#endif
+
ZeroconfInit(*main_loop);
player_create(instance->partition->pc);
@@ -523,6 +546,13 @@ int mpd_main(int argc, char *argv[])
listen_global_finish();
delete instance->client_list;
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ if (instance->neighbors != nullptr) {
+ instance->neighbors->Close();
+ delete instance->neighbors;
+ }
+#endif
+
const clock_t start = clock();
DatabaseGlobalDeinit();
FormatDebug(main_domain,
diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx
index ce753b300..750fd1219 100644
--- a/src/command/AllCommands.cxx
+++ b/src/command/AllCommands.cxx
@@ -27,6 +27,7 @@
#include "FileCommands.hxx"
#include "OutputCommands.hxx"
#include "MessageCommands.hxx"
+#include "NeighborCommands.hxx"
#include "OtherCommands.hxx"
#include "Permission.hxx"
#include "tag/TagType.h"
@@ -99,6 +100,9 @@ static const struct command commands[] = {
{ "list", PERMISSION_READ, 1, -1, handle_list },
{ "listall", PERMISSION_READ, 0, 1, handle_listall },
{ "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo },
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ { "listneighbors", PERMISSION_READ, 0, 0, handle_listneighbors },
+#endif
{ "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist },
{ "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo },
{ "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists },
@@ -179,6 +183,11 @@ command_available(gcc_unused const struct command *cmd)
return sticker_enabled();
#endif
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ if (strcmp(cmd->cmd, "listneighbors") == 0)
+ return neighbor_commands_available();
+#endif
+
return true;
}
diff --git a/src/command/NeighborCommands.cxx b/src/command/NeighborCommands.cxx
new file mode 100644
index 000000000..e68e8ef15
--- /dev/null
+++ b/src/command/NeighborCommands.cxx
@@ -0,0 +1,54 @@
+/*
+ * 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 "NeighborCommands.hxx"
+#include "client/Client.hxx"
+#include "Instance.hxx"
+#include "Main.hxx"
+#include "protocol/Result.hxx"
+#include "neighbor/Glue.hxx"
+#include "neighbor/Info.hxx"
+
+#include <set>
+#include <string>
+
+#include <assert.h>
+
+bool
+neighbor_commands_available()
+{
+ return instance->neighbors != nullptr;
+}
+
+CommandResult
+handle_listneighbors(Client &client,
+ gcc_unused int argc, gcc_unused char *argv[])
+{
+ assert(instance->neighbors != nullptr);
+
+ const auto neighbors = instance->neighbors->GetList();
+ for (const auto &i : neighbors)
+ client_printf(client,
+ "neighbor: %s\n"
+ "name: %s\n",
+ i.uri.c_str(),
+ i.display_name.c_str());
+ return CommandResult::OK;
+}
diff --git a/src/command/NeighborCommands.hxx b/src/command/NeighborCommands.hxx
new file mode 100644
index 000000000..7bad946b4
--- /dev/null
+++ b/src/command/NeighborCommands.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_NEIGHBOR_COMMANDS_HXX
+#define MPD_NEIGHBOR_COMMANDS_HXX
+
+#include "CommandResult.hxx"
+#include "Compiler.h"
+
+class Client;
+
+gcc_pure
+bool
+neighbor_commands_available();
+
+CommandResult
+handle_listneighbors(Client &client, int argc, char *argv[]);
+
+#endif
diff --git a/src/config/ConfigOption.hxx b/src/config/ConfigOption.hxx
index 4ee108196..506c9e9dc 100644
--- a/src/config/ConfigOption.hxx
+++ b/src/config/ConfigOption.hxx
@@ -77,6 +77,7 @@ enum ConfigOption {
CONF_DESPOTIFY_HIGH_BITRATE,
CONF_AUDIO_FILTER,
CONF_DATABASE,
+ CONF_NEIGHBORS,
CONF_MAX
};
diff --git a/src/config/ConfigTemplates.cxx b/src/config/ConfigTemplates.cxx
index e9cd68b5b..8eaa22bdd 100644
--- a/src/config/ConfigTemplates.cxx
+++ b/src/config/ConfigTemplates.cxx
@@ -77,6 +77,7 @@ const ConfigTemplate config_templates[] = {
{ "despotify_high_bitrate", false, false },
{ "filter", true, true },
{ "database", false, true },
+ { "neighbors", true, true },
};
static constexpr unsigned n_config_templates =
diff --git a/src/neighbor/Explorer.hxx b/src/neighbor/Explorer.hxx
new file mode 100644
index 000000000..84a54840c
--- /dev/null
+++ b/src/neighbor/Explorer.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_NEIGHBOR_EXPLORER_HXX
+#define MPD_NEIGHBOR_EXPLORER_HXX
+
+#include <forward_list>
+
+class Error;
+class NeighborListener;
+struct NeighborInfo;
+
+/**
+ * An object that explores the neighborhood for music servers.
+ *
+ * As soon as this object is opened, it will start exploring, and
+ * notify the #NeighborListener when it found or lost something.
+ *
+ * The implementation is supposed to be non-blocking. This can be
+ * implemented either using the #EventLoop instance that was passed to
+ * the NeighborPlugin or by moving the blocking parts in a dedicated
+ * thread.
+ */
+class NeighborExplorer {
+protected:
+ NeighborListener &listener;
+
+ explicit NeighborExplorer(NeighborListener &_listener)
+ :listener(_listener) {}
+
+public:
+ typedef std::forward_list<NeighborInfo> List;
+
+ /**
+ * Free instance data.
+ */
+ virtual ~NeighborExplorer() {}
+
+ /**
+ * Start exploring the neighborhood.
+ */
+ virtual bool Open(Error &error) = 0;
+
+ /**
+ * Stop exploring.
+ */
+ virtual void Close() = 0;
+
+ /**
+ * Obtain a list of currently known neighbors.
+ */
+ virtual List GetList() const = 0;
+};
+
+#endif
diff --git a/src/neighbor/Glue.cxx b/src/neighbor/Glue.cxx
new file mode 100644
index 000000000..cc0780d58
--- /dev/null
+++ b/src/neighbor/Glue.cxx
@@ -0,0 +1,111 @@
+/*
+ * 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 "Registry.hxx"
+#include "Explorer.hxx"
+#include "NeighborPlugin.hxx"
+#include "Info.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Error.hxx"
+
+NeighborGlue::Explorer::~Explorer()
+{
+ delete explorer;
+}
+
+NeighborGlue::~NeighborGlue() {}
+
+static NeighborExplorer *
+CreateNeighborExplorer(EventLoop &loop, NeighborListener &listener,
+ const config_param &param, Error &error)
+{
+ const char *plugin_name = param.GetBlockValue("plugin");
+ if (plugin_name == nullptr) {
+ error.Set(config_domain,
+ "Missing \"plugin\" configuration");
+ return nullptr;
+ }
+
+ const NeighborPlugin *plugin = GetNeighborPluginByName(plugin_name);
+ if (plugin == nullptr) {
+ error.Format(config_domain, "No such neighbor plugin: %s",
+ plugin_name);
+ return nullptr;
+ }
+
+ return plugin->create(loop, listener, param, error);
+}
+
+bool
+NeighborGlue::Init(EventLoop &loop, NeighborListener &listener, Error &error)
+{
+ const config_param *param = nullptr;
+ while ((param = config_get_next_param(CONF_NEIGHBORS, param))) {
+ NeighborExplorer *explorer =
+ CreateNeighborExplorer(loop, listener, *param, error);
+ if (explorer == nullptr) {
+ error.FormatPrefix("Line %i: ", param->line);
+ return false;
+ }
+
+ explorers.emplace_front(explorer);
+ }
+
+ return true;
+}
+
+bool
+NeighborGlue::Open(Error &error)
+{
+ for (auto i = explorers.begin(), end = explorers.end();
+ i != end; ++i) {
+ if (!i->explorer->Open(error)) {
+ /* roll back */
+ for (auto k = ++i; k != end; ++k)
+ k->explorer->Close();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void
+NeighborGlue::Close()
+{
+ for (auto i = explorers.begin(), end = explorers.end(); i != end; ++i)
+ i->explorer->Close();
+}
+
+NeighborGlue::List
+NeighborGlue::GetList() const
+{
+ List result;
+
+ for (const auto &i : explorers)
+ result.splice_after(result.before_begin(),
+ i.explorer->GetList());
+
+ return result;
+}
+
diff --git a/src/neighbor/Glue.hxx b/src/neighbor/Glue.hxx
new file mode 100644
index 000000000..92c612d22
--- /dev/null
+++ b/src/neighbor/Glue.hxx
@@ -0,0 +1,76 @@
+/*
+ * 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_NEIGHBOR_ALL_HXX
+#define MPD_NEIGHBOR_ALL_HXX
+
+#include "check.h"
+#include "Compiler.h"
+#include "thread/Mutex.hxx"
+
+#include <forward_list>
+
+struct config_param;
+class Error;
+class EventLoop;
+class NeighborExplorer;
+class NeighborListener;
+struct NeighborInfo;
+
+/**
+ * A class that initializes and opens all configured neighbor plugins.
+ */
+class NeighborGlue {
+ struct Explorer {
+ NeighborExplorer *const explorer;
+
+ Explorer(NeighborExplorer *_explorer):explorer(_explorer) {}
+ Explorer(const Explorer &) = delete;
+ ~Explorer();
+ };
+
+ Mutex mutex;
+
+ std::forward_list<Explorer> explorers;
+
+public:
+ typedef std::forward_list<NeighborInfo> List;
+
+ NeighborGlue() = default;
+ NeighborGlue(const NeighborGlue &) = delete;
+ ~NeighborGlue();
+
+ bool IsEmpty() const {
+ return explorers.empty();
+ }
+
+ bool Init(EventLoop &loop, NeighborListener &listener, Error &error);
+
+ bool Open(Error &error);
+ void Close();
+
+ /**
+ * Get the combined list of all neighbors from all active
+ * plugins.
+ */
+ gcc_pure
+ List GetList() const;
+};
+
+#endif
diff --git a/src/neighbor/Info.hxx b/src/neighbor/Info.hxx
new file mode 100644
index 000000000..3a34896e7
--- /dev/null
+++ b/src/neighbor/Info.hxx
@@ -0,0 +1,30 @@
+/*
+ * 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_NEIGHBOR_INFO_HXX
+#define MPD_NEIGHBOR_INFO_HXX
+
+#include <string>
+
+struct NeighborInfo {
+ std::string uri;
+ std::string display_name;
+};
+
+#endif
diff --git a/src/neighbor/Listener.hxx b/src/neighbor/Listener.hxx
new file mode 100644
index 000000000..20295f5a9
--- /dev/null
+++ b/src/neighbor/Listener.hxx
@@ -0,0 +1,36 @@
+/*
+ * 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_NEIGHBOR_LISTENER_HXX
+#define MPD_NEIGHBOR_LISTENER_HXX
+
+struct NeighborInfo;
+class NeighborExplorer;
+
+/**
+ * An interface that listens on events from neighbor plugins. The
+ * methods must be thread-safe and non-blocking.
+ */
+class NeighborListener {
+public:
+ virtual void FoundNeighbor(const NeighborInfo &info) = 0;
+ virtual void LostNeighbor(const NeighborInfo &info) = 0;
+};
+
+#endif
diff --git a/src/neighbor/NeighborPlugin.hxx b/src/neighbor/NeighborPlugin.hxx
new file mode 100644
index 000000000..0d4ebaa7b
--- /dev/null
+++ b/src/neighbor/NeighborPlugin.hxx
@@ -0,0 +1,40 @@
+/*
+ * 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_NEIGHBOR_PLUGIN_HXX
+#define MPD_NEIGHBOR_PLUGIN_HXX
+
+struct config_param;
+class Error;
+class EventLoop;
+class NeighborListener;
+class NeighborExplorer;
+
+struct NeighborPlugin {
+ const char *name;
+
+ /**
+ * Allocates and configures a #NeighborExplorer instance.
+ */
+ NeighborExplorer *(*create)(EventLoop &loop, NeighborListener &listener,
+ const config_param &param,
+ Error &error);
+};
+
+#endif
diff --git a/src/neighbor/Registry.cxx b/src/neighbor/Registry.cxx
new file mode 100644
index 000000000..fc9d05ecf
--- /dev/null
+++ b/src/neighbor/Registry.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "Registry.hxx"
+#include "NeighborPlugin.hxx"
+#include "plugins/SmbclientNeighborPlugin.hxx"
+
+#include <string.h>
+
+const NeighborPlugin *const neighbor_plugins[] = {
+#ifdef ENABLE_SMBCLIENT
+ &smbclient_neighbor_plugin,
+#endif
+ nullptr
+};
+
+const NeighborPlugin *
+GetNeighborPluginByName(const char *name)
+{
+ for (auto i = neighbor_plugins; *i != nullptr; ++i)
+ if (strcmp((*i)->name, name) == 0)
+ return *i;
+
+ return nullptr;
+}
diff --git a/src/neighbor/Registry.hxx b/src/neighbor/Registry.hxx
new file mode 100644
index 000000000..0b89e537d
--- /dev/null
+++ b/src/neighbor/Registry.hxx
@@ -0,0 +1,37 @@
+/*
+ * 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_NEIGHBOR_REGISTRY_HXX
+#define MPD_NEIGHBOR_REGISTRY_HXX
+
+#include "Compiler.h"
+
+struct NeighborPlugin;
+
+/**
+ * nullptr terminated list of all neighbor plugins which were enabled at
+ * compile time.
+ */
+extern const NeighborPlugin *const neighbor_plugins[];
+
+gcc_pure
+const NeighborPlugin *
+GetNeighborPluginByName(const char *name);
+
+#endif
diff --git a/src/neighbor/plugins/SmbclientNeighborPlugin.cxx b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx
new file mode 100644
index 000000000..8dc537e94
--- /dev/null
+++ b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx
@@ -0,0 +1,285 @@
+/*
+ * 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 "SmbclientNeighborPlugin.hxx"
+#include "lib/smbclient/Init.hxx"
+#include "neighbor/NeighborPlugin.hxx"
+#include "neighbor/Explorer.hxx"
+#include "neighbor/Listener.hxx"
+#include "neighbor/Info.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "thread/Thread.hxx"
+#include "thread/Name.hxx"
+#include "util/Macros.hxx"
+#include "util/Domain.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <libsmbclient.h>
+
+#include <list>
+#include <algorithm>
+
+static constexpr Domain smbclient_domain("smbclient");
+
+class SmbclientNeighborExplorer final : public NeighborExplorer {
+ struct Server {
+ std::string name, comment;
+
+ bool alive;
+
+ Server(std::string &&_name, std::string &&_comment)
+ :name(std::move(_name)), comment(std::move(_comment)),
+ alive(true) {}
+ Server(const Server &) = delete;
+
+ gcc_pure
+ bool operator==(const Server &other) const {
+ return name == other.name;
+ }
+
+ gcc_pure
+ NeighborInfo Export() const {
+ return { "smb://" + name + "/", comment };
+ }
+ };
+
+ Thread thread;
+
+ mutable Mutex mutex;
+ Cond cond;
+
+ List list;
+
+ bool quit;
+
+public:
+ SmbclientNeighborExplorer(NeighborListener &_listener)
+ :NeighborExplorer(_listener) {}
+
+ /* virtual methods from class NeighborExplorer */
+ virtual bool Open(Error &error) override;
+ virtual void Close() override;
+ virtual List GetList() const override;
+
+private:
+ void Run();
+ void ThreadFunc();
+ static void ThreadFunc(void *ctx);
+};
+
+bool
+SmbclientNeighborExplorer::Open(Error &error)
+{
+ quit = false;
+ return thread.Start(ThreadFunc, this, error);
+}
+
+void
+SmbclientNeighborExplorer::Close()
+{
+ mutex.lock();
+ quit = true;
+ cond.signal();
+ mutex.unlock();
+
+ thread.Join();
+}
+
+NeighborExplorer::List
+SmbclientNeighborExplorer::GetList() const
+{
+ const ScopeLock protect(mutex);
+ /*
+ List list;
+ for (const auto &i : servers)
+ list.emplace_front(i.Export());
+ */
+ return list;
+}
+
+static void
+ReadServer(NeighborExplorer::List &list, const smbc_dirent &e)
+{
+ const std::string name(e.name, e.namelen);
+ const std::string comment(e.comment, e.commentlen);
+
+ NeighborInfo info{
+ "smb://" + name,
+ name + " (" + comment + ")",
+ };
+
+ list.emplace_front(std::move(info));
+}
+
+static void
+ReadServers(NeighborExplorer::List &list, const char *uri);
+
+static void
+ReadWorkgroup(NeighborExplorer::List &list, const std::string &name)
+{
+ std::string uri = "smb://" + name;
+ ReadServers(list, uri.c_str());
+}
+
+static void
+ReadEntry(NeighborExplorer::List &list, const smbc_dirent &e)
+{
+ switch (e.smbc_type) {
+ case SMBC_WORKGROUP:
+ ReadWorkgroup(list, std::string(e.name, e.namelen));
+ break;
+
+ case SMBC_SERVER:
+ ReadServer(list, e);
+ break;
+ }
+}
+
+static void
+ReadServers(NeighborExplorer::List &list, int fd)
+{
+ smbc_dirent *e;
+ while ((e = smbc_readdir(fd)) != nullptr)
+ ReadEntry(list, *e);
+
+ smbc_closedir(fd);
+}
+
+static void
+ReadServers(NeighborExplorer::List &list, const char *uri)
+{
+ int fd = smbc_opendir(uri);
+ if (fd >= 0) {
+ ReadServers(list, fd);
+ smbc_closedir(fd);
+ } else
+ FormatErrno(smbclient_domain, "smbc_opendir('%s') failed",
+ uri);
+}
+
+gcc_pure
+static NeighborExplorer::List
+DetectServers()
+{
+ NeighborExplorer::List list;
+ ReadServers(list, "smb://");
+ return list;
+}
+
+gcc_pure
+static NeighborExplorer::List::const_iterator
+FindBeforeServerByURI(NeighborExplorer::List::const_iterator prev,
+ NeighborExplorer::List::const_iterator end,
+ const std::string &uri)
+{
+ for (auto i = std::next(prev); i != end; prev = i, i = std::next(prev))
+ if (i->uri == uri)
+ return prev;
+
+ return end;
+}
+
+inline void
+SmbclientNeighborExplorer::Run()
+{
+ List found = DetectServers(), lost;
+
+ mutex.lock();
+
+ const auto found_before_begin = found.before_begin();
+ const auto found_end = found.end();
+
+ for (auto prev = list.before_begin(), i = std::next(prev), end = list.end();
+ i != end; i = std::next(prev)) {
+ auto f = FindBeforeServerByURI(found_before_begin, found_end,
+ i->uri);
+ if (f != found_end) {
+ /* still visible: remove from "found" so we
+ don't believe it's a new one */
+ *i = std::move(*std::next(f));
+ found.erase_after(f);
+ prev = i;
+ } else {
+ /* can't see it anymore: move to "lost" */
+ lost.splice_after(lost.before_begin(), list, prev);
+ }
+ }
+
+ for (auto prev = found_before_begin, i = std::next(prev);
+ i != found_end; prev = i, i = std::next(prev))
+ list.push_front(*i);
+
+ mutex.unlock();
+
+ for (auto &i : lost)
+ listener.LostNeighbor(i);
+
+ for (auto &i : found)
+ listener.FoundNeighbor(i);
+}
+
+inline void
+SmbclientNeighborExplorer::ThreadFunc()
+{
+ mutex.lock();
+
+ while (!quit) {
+ mutex.unlock();
+
+ Run();
+
+ mutex.lock();
+ if (quit)
+ break;
+
+ // TODO: sleep for how long?
+ cond.timed_wait(mutex, 10000);
+ }
+
+ mutex.unlock();
+}
+
+void
+SmbclientNeighborExplorer::ThreadFunc(void *ctx)
+{
+ SetThreadName("smbclient");
+
+ SmbclientNeighborExplorer &e = *(SmbclientNeighborExplorer *)ctx;
+ e.ThreadFunc();
+}
+
+static NeighborExplorer *
+smbclient_neighbor_create(gcc_unused EventLoop &loop,
+ NeighborListener &listener,
+ gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ if (!SmbclientInit(error))
+ return nullptr;
+
+ return new SmbclientNeighborExplorer(listener);
+}
+
+const NeighborPlugin smbclient_neighbor_plugin = {
+ "smbclient",
+ smbclient_neighbor_create,
+};
diff --git a/src/neighbor/plugins/SmbclientNeighborPlugin.hxx b/src/neighbor/plugins/SmbclientNeighborPlugin.hxx
new file mode 100644
index 000000000..12ec9c0cd
--- /dev/null
+++ b/src/neighbor/plugins/SmbclientNeighborPlugin.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_NEIGHBOR_SMBCLIENT_HXX
+#define MPD_NEIGHBOR_SMBCLIENT_HXX
+
+struct NeighborPlugin;
+
+extern const NeighborPlugin smbclient_neighbor_plugin;
+
+#endif
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 000000000..132ce5fcf
--- /dev/null
+++ b/test/.gitignore
@@ -0,0 +1 @@
+/run_neighbor_explorer
diff --git a/test/run_neighbor_explorer.cxx b/test/run_neighbor_explorer.cxx
new file mode 100644
index 000000000..374114b11
--- /dev/null
+++ b/test/run_neighbor_explorer.cxx
@@ -0,0 +1,85 @@
+/*
+ * 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 "config/ConfigGlobal.hxx"
+#include "neighbor/Listener.hxx"
+#include "neighbor/Info.hxx"
+#include "neighbor/Glue.hxx"
+#include "fs/Path.hxx"
+#include "event/Loop.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+class MyNeighborListener final : public NeighborListener {
+ public:
+ /* virtual methods from class NeighborListener */
+ virtual void FoundNeighbor(const NeighborInfo &info) override {
+ printf("found '%s' (%s)\n",
+ info.display_name.c_str(), info.uri.c_str());
+ }
+
+ virtual void LostNeighbor(const NeighborInfo &info) override {
+ printf("lost '%s' (%s)\n",
+ info.display_name.c_str(), info.uri.c_str());
+ }
+};
+
+int
+main(int argc, char **argv)
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: run_neighbor_explorer CONFIG\n");
+ return EXIT_FAILURE;
+ }
+
+ const Path config_path = Path::FromFS(argv[1]);
+
+ /* read configuration file (mpd.conf) */
+
+ Error error;
+
+ config_global_init();
+ if (!ReadConfigFile(config_path, error)) {
+ LogError(error);
+ return EXIT_FAILURE;
+ }
+
+ /* initialize the core */
+
+ EventLoop loop((EventLoop::Default()));
+
+ /* initialize neighbor plugins */
+
+ MyNeighborListener listener;
+ NeighborGlue neighbor;
+ if (!neighbor.Init(loop, listener, error) || !neighbor.Open(error)) {
+ LogError(error);
+ return EXIT_FAILURE;
+ }
+
+ /* run */
+
+ loop.Run();
+ neighbor.Close();
+ return EXIT_SUCCESS;
+}