/* * 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 "lib/smbclient/Domain.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 #include #include 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); list.emplace_front("smb://" + name, name + " (" + comment + ")"); } 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" */ #if defined(__clang__) || GCC_CHECK_VERSION(4,7) lost.splice_after(lost.before_begin(), list, prev); #else /* the forward_list::splice_after() lvalue reference overload is missing in gcc 4.6 */ lost.emplace_front(std::move(*i)); list.erase_after(prev); #endif } } 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, };