diff options
-rw-r--r-- | Makefile.am | 54 | ||||
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | configure.ac | 21 | ||||
-rw-r--r-- | src/CommandLine.cxx | 14 | ||||
-rw-r--r-- | src/Idle.cxx | 1 | ||||
-rw-r--r-- | src/Idle.hxx | 3 | ||||
-rw-r--r-- | src/Instance.cxx | 16 | ||||
-rw-r--r-- | src/Instance.hxx | 22 | ||||
-rw-r--r-- | src/Main.cxx | 30 | ||||
-rw-r--r-- | src/command/AllCommands.cxx | 9 | ||||
-rw-r--r-- | src/command/NeighborCommands.cxx | 54 | ||||
-rw-r--r-- | src/command/NeighborCommands.hxx | 35 | ||||
-rw-r--r-- | src/config/ConfigOption.hxx | 1 | ||||
-rw-r--r-- | src/config/ConfigTemplates.cxx | 1 | ||||
-rw-r--r-- | src/neighbor/Explorer.hxx | 71 | ||||
-rw-r--r-- | src/neighbor/Glue.cxx | 111 | ||||
-rw-r--r-- | src/neighbor/Glue.hxx | 76 | ||||
-rw-r--r-- | src/neighbor/Info.hxx | 30 | ||||
-rw-r--r-- | src/neighbor/Listener.hxx | 36 | ||||
-rw-r--r-- | src/neighbor/NeighborPlugin.hxx | 40 | ||||
-rw-r--r-- | src/neighbor/Registry.cxx | 42 | ||||
-rw-r--r-- | src/neighbor/Registry.hxx | 37 | ||||
-rw-r--r-- | src/neighbor/plugins/SmbclientNeighborPlugin.cxx | 285 | ||||
-rw-r--r-- | src/neighbor/plugins/SmbclientNeighborPlugin.hxx | 27 | ||||
-rw-r--r-- | test/.gitignore | 1 | ||||
-rw-r--r-- | test/run_neighbor_explorer.cxx | 85 |
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 = \ @@ -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 ¶m, 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 ¶m, + 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 ¶m, + 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; +} |