SmbclientNeighborPlugin.cxx 6.04 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2020 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 * 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 "SmbclientNeighborPlugin.hxx"
#include "lib/smbclient/Init.hxx"
22
#include "lib/smbclient/Context.hxx"
23
#include "lib/smbclient/Domain.hxx"
24 25 26 27 28 29 30 31 32 33 34 35
#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>

36
#include <utility>
37 38 39

class SmbclientNeighborExplorer final : public NeighborExplorer {
	struct Server {
40
		const std::string name, comment;
41

42
		Server(std::string &&_name, std::string &&_comment) noexcept
43 44 45
			:name(std::move(_name)),
			 comment(std::move(_comment)) {}

46 47 48
		Server(const Server &) = delete;

		gcc_pure
49
		bool operator==(const Server &other) const noexcept {
50 51 52
			return name == other.name;
		}

53
		[[nodiscard]] gcc_pure
54
		NeighborInfo Export() const noexcept {
55 56 57 58
			return { "smb://" + name + "/", comment };
		}
	};

59 60
	SmbclientContext ctx = SmbclientContext::New();

61 62 63 64 65 66 67 68 69 70
	Thread thread;

	mutable Mutex mutex;
	Cond cond;

	List list;

	bool quit;

public:
71
	explicit SmbclientNeighborExplorer(NeighborListener &_listener)
72 73
		:NeighborExplorer(_listener),
		 thread(BIND_THIS_METHOD(ThreadFunc)) {}
74 75

	/* virtual methods from class NeighborExplorer */
76
	void Open() override;
77 78
	void Close() noexcept override;
	List GetList() const noexcept override;
79 80

private:
81 82 83
	/**
	 * Caller must lock the mutex.
	 */
84
	void Run() noexcept;
85

86
	void ThreadFunc() noexcept;
87 88
};

89 90
void
SmbclientNeighborExplorer::Open()
91 92
{
	quit = false;
93
	thread.Start();
94 95 96
}

void
97
SmbclientNeighborExplorer::Close() noexcept
98
{
99 100 101
	{
		const std::lock_guard<Mutex> lock(mutex);
		quit = true;
102
		cond.notify_one();
103
	}
104 105 106 107 108

	thread.Join();
}

NeighborExplorer::List
109
SmbclientNeighborExplorer::GetList() const noexcept
110
{
111
	const std::lock_guard<Mutex> protect(mutex);
112 113 114 115
	return list;
}

static void
116
ReadServer(NeighborExplorer::List &list, const smbc_dirent &e) noexcept
117 118 119 120
{
	const std::string name(e.name, e.namelen);
	const std::string comment(e.comment, e.commentlen);

121
	list.emplace_front("smb://" + name, name + " (" + comment + ")");
122 123 124
}

static void
125 126
ReadServers(SmbclientContext &ctx, const char *uri,
	    NeighborExplorer::List &list) noexcept;
127 128

static void
129 130
ReadWorkgroup(SmbclientContext &ctx, const std::string &name,
	      NeighborExplorer::List &list) noexcept
131 132
{
	std::string uri = "smb://" + name;
133
	ReadServers(ctx, uri.c_str(), list);
134 135 136
}

static void
137 138
ReadEntry(SmbclientContext &ctx, const smbc_dirent &e,
	  NeighborExplorer::List &list) noexcept
139 140 141
{
	switch (e.smbc_type) {
	case SMBC_WORKGROUP:
142
		ReadWorkgroup(ctx, std::string(e.name, e.namelen), list);
143 144 145 146 147 148 149 150 151
		break;

	case SMBC_SERVER:
		ReadServer(list, e);
		break;
	}
}

static void
152 153
ReadServers(SmbclientContext &ctx, SMBCFILE *handle,
	    NeighborExplorer::List &list) noexcept
154
{
155 156
	while (auto e = ctx.ReadDirectory(handle))
		ReadEntry(ctx, *e, list);
157 158 159
}

static void
160 161
ReadServers(SmbclientContext &ctx, const char *uri,
	    NeighborExplorer::List &list) noexcept
162
{
163 164 165 166
	SMBCFILE *handle = ctx.OpenDirectory(uri);
	if (handle != nullptr) {
		ReadServers(ctx, handle, list);
		ctx.CloseDirectory(handle);
167 168 169 170 171 172 173
	} else
		FormatErrno(smbclient_domain, "smbc_opendir('%s') failed",
			    uri);
}

gcc_pure
static NeighborExplorer::List
174
DetectServers(SmbclientContext &ctx) noexcept
175 176
{
	NeighborExplorer::List list;
177
	ReadServers(ctx, "smb://", list);
178 179 180 181
	return list;
}

gcc_pure
182 183 184
static NeighborExplorer::List::iterator
FindBeforeServerByURI(NeighborExplorer::List::iterator prev,
		      NeighborExplorer::List::iterator end,
185
		      const std::string &uri) noexcept
186 187 188 189 190 191 192 193 194
{
	for (auto i = std::next(prev); i != end; prev = i, i = std::next(prev))
		if (i->uri == uri)
			return prev;

	return end;
}

inline void
195
SmbclientNeighborExplorer::Run() noexcept
196
{
197
	List found, lost;
198

199 200
	{
		const ScopeUnlock unlock(mutex);
201
		found = DetectServers(ctx);
202
	}
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226

	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);

227
	const ScopeUnlock unlock(mutex);
228 229 230 231 232 233 234 235 236

	for (auto &i : lost)
		listener.LostNeighbor(i);

	for (auto &i : found)
		listener.FoundNeighbor(i);
}

inline void
237
SmbclientNeighborExplorer::ThreadFunc() noexcept
238
{
239 240
	SetThreadName("smbclient");

241
	std::unique_lock<Mutex> lock(mutex);
242 243 244 245 246 247 248 249

	while (!quit) {
		Run();

		if (quit)
			break;

		// TODO: sleep for how long?
250
		cond.wait_for(lock, std::chrono::seconds(10));
251 252 253
	}
}

254
static std::unique_ptr<NeighborExplorer>
Rosen Penev's avatar
Rosen Penev committed
255
smbclient_neighbor_create([[maybe_unused]] EventLoop &loop,
256
			  NeighborListener &listener,
Rosen Penev's avatar
Rosen Penev committed
257
			  [[maybe_unused]] const ConfigBlock &block)
258
{
259
	SmbclientInit();
260

261
	return std::make_unique<SmbclientNeighborExplorer>(listener);
262 263 264 265 266 267
}

const NeighborPlugin smbclient_neighbor_plugin = {
	"smbclient",
	smbclient_neighbor_create,
};