SmbclientNeighborPlugin.cxx 5.91 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/Domain.hxx"
23
#include "lib/smbclient/Mutex.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;
		}

Rosen Penev's avatar
Rosen Penev committed
53
		[[nodiscard]] gcc_pure
54
		NeighborInfo Export() const noexcept {
55 56 57 58 59 60 61 62 63 64 65 66 67 68
			return { "smb://" + name + "/", comment };
		}
	};

	Thread thread;

	mutable Mutex mutex;
	Cond cond;

	List list;

	bool quit;

public:
Max Kellermann's avatar
Max Kellermann committed
69
	explicit SmbclientNeighborExplorer(NeighborListener &_listener) noexcept
70 71
		:NeighborExplorer(_listener),
		 thread(BIND_THIS_METHOD(ThreadFunc)) {}
72 73

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

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

84
	void ThreadFunc() noexcept;
85 86
};

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

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

	thread.Join();
}

NeighborExplorer::List
107
SmbclientNeighborExplorer::GetList() const noexcept
108
{
109
	const std::lock_guard<Mutex> protect(mutex);
110 111 112 113 114 115 116 117 118
	/*
	List list;
	for (const auto &i : servers)
		list.emplace_front(i.Export());
	*/
	return list;
}

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

124
	list.emplace_front("smb://" + name, name + " (" + comment + ")");
125 126 127
}

static void
128
ReadServers(NeighborExplorer::List &list, const char *uri) noexcept;
129 130

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

static void
138
ReadEntry(NeighborExplorer::List &list, const smbc_dirent &e) noexcept
139 140 141 142 143 144 145 146 147 148 149 150 151
{
	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
152
ReadServers(NeighborExplorer::List &list, int fd) noexcept
153 154 155 156 157 158 159
{
	smbc_dirent *e;
	while ((e = smbc_readdir(fd)) != nullptr)
		ReadEntry(list, *e);
}

static void
160
ReadServers(NeighborExplorer::List &list, const char *uri) noexcept
161 162 163 164 165 166 167 168 169 170 171 172
{
	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
173
DetectServers() noexcept
174 175
{
	NeighborExplorer::List list;
176
	const std::lock_guard<Mutex> protect(smbclient_mutex);
177 178 179 180 181
	ReadServers(list, "smb://");
	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 201 202
	{
		const ScopeUnlock unlock(mutex);
		found = DetectServers();
	}
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>
255 256
smbclient_neighbor_create(gcc_unused EventLoop &loop,
			  NeighborListener &listener,
257
			  gcc_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,
};