mpd/src/neighbor/plugins/SmbclientNeighborPlugin.cxx
2023-03-06 14:59:48 +01:00

254 lines
5.4 KiB
C++

// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "SmbclientNeighborPlugin.hxx"
#include "lib/smbclient/Init.hxx"
#include "lib/smbclient/Context.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 "Log.hxx"
#include <libsmbclient.h>
#include <cerrno>
#include <cstring>
#include <utility>
class SmbclientNeighborExplorer final : public NeighborExplorer {
struct Server {
const std::string name, comment;
Server(std::string &&_name, std::string &&_comment) noexcept
:name(std::move(_name)),
comment(std::move(_comment)) {}
Server(const Server &) = delete;
[[gnu::pure]]
bool operator==(const Server &other) const noexcept {
return name == other.name;
}
[[nodiscard]] [[gnu::pure]]
NeighborInfo Export() const noexcept {
return { "smb://" + name + "/", comment };
}
};
SmbclientContext ctx = SmbclientContext::New();
Thread thread;
mutable Mutex mutex;
Cond cond;
List list;
bool quit;
public:
explicit SmbclientNeighborExplorer(NeighborListener &_listener)
:NeighborExplorer(_listener),
thread(BIND_THIS_METHOD(ThreadFunc)) {}
/* virtual methods from class NeighborExplorer */
void Open() override;
void Close() noexcept override;
List GetList() const noexcept override;
private:
/**
* Caller must lock the mutex.
*/
void Run() noexcept;
void ThreadFunc() noexcept;
};
void
SmbclientNeighborExplorer::Open()
{
quit = false;
thread.Start();
}
void
SmbclientNeighborExplorer::Close() noexcept
{
{
const std::scoped_lock<Mutex> lock(mutex);
quit = true;
cond.notify_one();
}
thread.Join();
}
NeighborExplorer::List
SmbclientNeighborExplorer::GetList() const noexcept
{
const std::scoped_lock<Mutex> protect(mutex);
return list;
}
static void
ReadServer(NeighborExplorer::List &list, const smbc_dirent &e) noexcept
{
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(SmbclientContext &ctx, const char *uri,
NeighborExplorer::List &list) noexcept;
static void
ReadWorkgroup(SmbclientContext &ctx, const std::string &name,
NeighborExplorer::List &list) noexcept
{
std::string uri = "smb://" + name;
ReadServers(ctx, uri.c_str(), list);
}
static void
ReadEntry(SmbclientContext &ctx, const smbc_dirent &e,
NeighborExplorer::List &list) noexcept
{
switch (e.smbc_type) {
case SMBC_WORKGROUP:
ReadWorkgroup(ctx, std::string(e.name, e.namelen), list);
break;
case SMBC_SERVER:
ReadServer(list, e);
break;
}
}
static void
ReadServers(SmbclientContext &ctx, SMBCFILE *handle,
NeighborExplorer::List &list) noexcept
{
while (auto e = ctx.ReadDirectory(handle))
ReadEntry(ctx, *e, list);
}
static void
ReadServers(SmbclientContext &ctx, const char *uri,
NeighborExplorer::List &list) noexcept
{
SMBCFILE *handle = ctx.OpenDirectory(uri);
if (handle != nullptr) {
ReadServers(ctx, handle, list);
ctx.CloseDirectory(handle);
} else
FmtError(smbclient_domain, "smbc_opendir('{}') failed: {}",
uri, strerror(errno));
}
[[gnu::pure]]
static NeighborExplorer::List
DetectServers(SmbclientContext &ctx) noexcept
{
NeighborExplorer::List list;
ReadServers(ctx, "smb://", list);
return list;
}
[[gnu::pure]]
static NeighborExplorer::List::iterator
FindBeforeServerByURI(NeighborExplorer::List::iterator prev,
NeighborExplorer::List::iterator end,
const std::string &uri) noexcept
{
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() noexcept
{
List found, lost;
{
const ScopeUnlock unlock(mutex);
found = DetectServers(ctx);
}
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);
const ScopeUnlock unlock(mutex);
for (auto &i : lost)
listener.LostNeighbor(i);
for (auto &i : found)
listener.FoundNeighbor(i);
}
inline void
SmbclientNeighborExplorer::ThreadFunc() noexcept
{
SetThreadName("smbclient");
std::unique_lock<Mutex> lock(mutex);
while (!quit) {
Run();
if (quit)
break;
// TODO: sleep for how long?
cond.wait_for(lock, std::chrono::seconds(10));
}
}
static std::unique_ptr<NeighborExplorer>
smbclient_neighbor_create([[maybe_unused]] EventLoop &loop,
NeighborListener &listener,
[[maybe_unused]] const ConfigBlock &block)
{
SmbclientInit();
return std::make_unique<SmbclientNeighborExplorer>(listener);
}
const NeighborPlugin smbclient_neighbor_plugin = {
"smbclient",
smbclient_neighbor_create,
};