UdisksNeighborPlugin.cxx 6.68 KB
Newer Older
Max Kellermann's avatar
Max Kellermann committed
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 The Music Player Daemon Project
Max Kellermann's avatar
Max Kellermann committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 * 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 "UdisksNeighborPlugin.hxx"
#include "lib/dbus/Connection.hxx"
#include "lib/dbus/Error.hxx"
23
#include "lib/dbus/Glue.hxx"
24
#include "lib/dbus/FilterHelper.hxx"
Max Kellermann's avatar
Max Kellermann committed
25
#include "lib/dbus/Message.hxx"
26
#include "lib/dbus/AsyncRequest.hxx"
Max Kellermann's avatar
Max Kellermann committed
27 28 29 30 31 32 33
#include "lib/dbus/ReadIter.hxx"
#include "lib/dbus/ObjectManager.hxx"
#include "lib/dbus/UDisks2.hxx"
#include "neighbor/NeighborPlugin.hxx"
#include "neighbor/Explorer.hxx"
#include "neighbor/Listener.hxx"
#include "neighbor/Info.hxx"
34
#include "event/Call.hxx"
Max Kellermann's avatar
Max Kellermann committed
35
#include "thread/Mutex.hxx"
36
#include "thread/SafeSingleton.hxx"
37
#include "util/Manual.hxx"
Max Kellermann's avatar
Max Kellermann committed
38 39 40 41 42
#include "Log.hxx"

#include <string>
#include <map>

43 44 45 46 47
static NeighborInfo
ToNeighborInfo(const UDisks2::Object &o) noexcept
{
	return {o.GetUri(), o.path};
}
Max Kellermann's avatar
Max Kellermann committed
48

49 50 51 52 53
static constexpr char udisks_neighbor_match[] =
	"type='signal',sender='" UDISKS2_INTERFACE "',"
	"interface='" DBUS_OM_INTERFACE "',"
	"path='" UDISKS2_PATH "'";

Max Kellermann's avatar
Max Kellermann committed
54
class UdisksNeighborExplorer final
55
	: public NeighborExplorer {
Max Kellermann's avatar
Max Kellermann committed
56

57 58
	EventLoop &event_loop;

59
	Manual<SafeSingleton<ODBus::Glue>> dbus_glue;
Max Kellermann's avatar
Max Kellermann committed
60

61 62
	ODBus::FilterHelper filter_helper;

63
	ODBus::AsyncRequest list_request;
Max Kellermann's avatar
Max Kellermann committed
64 65 66 67 68 69 70 71 72 73 74

	/**
	 * Protects #by_uri, #by_path.
	 */
	mutable Mutex mutex;

	using ByUri = std::map<std::string, NeighborInfo>;
	ByUri by_uri;
	std::map<std::string, ByUri::iterator> by_path;

public:
75
	UdisksNeighborExplorer(EventLoop &_event_loop,
Max Kellermann's avatar
Max Kellermann committed
76
			       NeighborListener &_listener) noexcept
77
		:NeighborExplorer(_listener), event_loop(_event_loop) {}
Max Kellermann's avatar
Max Kellermann committed
78

79
	auto &GetEventLoop() const noexcept {
80 81 82 83
		return event_loop;
	}

	auto &&GetConnection() noexcept {
84
		return dbus_glue.Get()->GetConnection();
Max Kellermann's avatar
Max Kellermann committed
85 86 87 88 89 90 91 92
	}

	/* virtual methods from class NeighborExplorer */
	void Open() override;
	void Close() noexcept override;
	List GetList() const noexcept override;

private:
93 94 95
	void DoOpen();
	void DoClose() noexcept;

96
	void Insert(UDisks2::Object &&o) noexcept;
Max Kellermann's avatar
Max Kellermann committed
97 98
	void Remove(const std::string &path) noexcept;

99
	void OnListNotify(ODBus::Message reply) noexcept;
Max Kellermann's avatar
Max Kellermann committed
100 101 102 103 104

	DBusHandlerResult HandleMessage(DBusConnection *dbus_connection,
					DBusMessage *message) noexcept;
};

105 106
inline void
UdisksNeighborExplorer::DoOpen()
Max Kellermann's avatar
Max Kellermann committed
107 108 109
{
	using namespace ODBus;

110
	dbus_glue.Construct(event_loop);
Max Kellermann's avatar
Max Kellermann committed
111

112
	auto &connection = GetConnection();
Max Kellermann's avatar
Max Kellermann committed
113

114 115
	/* this ugly try/catch cascade is only here because this
	   method has no RAII for this method - TODO: improve this */
Max Kellermann's avatar
Max Kellermann committed
116 117
	try {
		Error error;
118
		dbus_bus_add_match(connection, udisks_neighbor_match, error);
Max Kellermann's avatar
Max Kellermann committed
119 120
		error.CheckThrow("DBus AddMatch error");

121
		try {
122 123
			filter_helper.Add(connection,
					  BIND_THIS_METHOD(HandleMessage));
124 125 126 127 128 129

			try {
				auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
								  UDISKS2_PATH,
								  DBUS_OM_INTERFACE,
								  "GetManagedObjects");
130 131 132
				list_request.Send(connection, *msg.Get(), [this](auto o) {
					return OnListNotify(std::move(o));
				});
133
			} catch (...) {
134
				filter_helper.Remove();
135 136 137 138 139 140 141
				throw;
			}
		} catch (...) {
			dbus_bus_remove_match(connection,
					      udisks_neighbor_match, nullptr);
			throw;
		}
Max Kellermann's avatar
Max Kellermann committed
142
	} catch (...) {
143
		dbus_glue.Destruct();
Max Kellermann's avatar
Max Kellermann committed
144 145 146 147 148
		throw;
	}
}

void
149
UdisksNeighborExplorer::Open()
Max Kellermann's avatar
Max Kellermann committed
150
{
151 152
	BlockingCall(GetEventLoop(), [this](){ DoOpen(); });
}
Max Kellermann's avatar
Max Kellermann committed
153

154 155 156
inline void
UdisksNeighborExplorer::DoClose() noexcept
{
157 158
	if (list_request) {
		list_request.Cancel();
Max Kellermann's avatar
Max Kellermann committed
159 160
	}

161 162
	auto &connection = GetConnection();

163
	filter_helper.Remove();
164
	dbus_bus_remove_match(connection, udisks_neighbor_match, nullptr);
Max Kellermann's avatar
Max Kellermann committed
165

166
	dbus_glue.Destruct();
Max Kellermann's avatar
Max Kellermann committed
167 168
}

169 170 171 172 173 174
void
UdisksNeighborExplorer::Close() noexcept
{
	BlockingCall(GetEventLoop(), [this](){ DoClose(); });
}

Max Kellermann's avatar
Max Kellermann committed
175 176 177 178 179 180 181 182 183 184 185 186 187
NeighborExplorer::List
UdisksNeighborExplorer::GetList() const noexcept
{
	const std::lock_guard<Mutex> lock(mutex);

	NeighborExplorer::List result;

	for (const auto &i : by_uri)
		result.emplace_front(i.second);
	return result;
}

void
188
UdisksNeighborExplorer::Insert(UDisks2::Object &&o) noexcept
Max Kellermann's avatar
Max Kellermann committed
189 190 191
{
	assert(o.IsValid());

192
	const NeighborInfo info = ToNeighborInfo(o);
Max Kellermann's avatar
Max Kellermann committed
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225

	{
		const std::lock_guard<Mutex> protect(mutex);
		auto i = by_uri.emplace(std::make_pair(o.GetUri(), info));
		if (!i.second)
			i.first->second = info;

		by_path.emplace(std::make_pair(o.path, i.first));
		// TODO: do we need to remove a conflicting path?
	}

	listener.FoundNeighbor(info);
}

void
UdisksNeighborExplorer::Remove(const std::string &path) noexcept
{
	std::unique_lock<Mutex> lock(mutex);

	auto i = by_path.find(path);
	if (i == by_path.end())
		return;

	const auto info = std::move(i->second->second);

	by_uri.erase(i->second);
	by_path.erase(i);

	lock.unlock();
	listener.LostNeighbor(info);
}

inline void
226
UdisksNeighborExplorer::OnListNotify(ODBus::Message reply) noexcept
Max Kellermann's avatar
Max Kellermann committed
227
{
228
	try{
229 230
		UDisks2::ParseObjects(reply,
				      [this](auto p) { return Insert(std::move(p)); });
Max Kellermann's avatar
Max Kellermann committed
231
	} catch (...) {
232 233
		LogError(std::current_exception(),
			 "Failed to parse GetManagedObjects reply");
Max Kellermann's avatar
Max Kellermann committed
234 235 236 237 238 239 240 241 242 243 244
		return;
	}
}

inline DBusHandlerResult
UdisksNeighborExplorer::HandleMessage(DBusConnection *, DBusMessage *message) noexcept
{
	using namespace ODBus;

	if (dbus_message_is_signal(message, DBUS_OM_INTERFACE,
				   "InterfacesAdded") &&
245
	    dbus_message_has_signature(message, InterfacesAddedType::as_string)) {
246
		RecurseInterfaceDictEntry(ReadMessageIter(*message), [this](const char *path, auto &&i){
247
				UDisks2::Object o(path);
248
				UDisks2::ParseObject(o, std::forward<decltype(i)>(i));
249
				if (o.IsValid())
250
					this->Insert(std::move(o));
251
			});
Max Kellermann's avatar
Max Kellermann committed
252 253 254 255

		return DBUS_HANDLER_RESULT_HANDLED;
	} else if (dbus_message_is_signal(message, DBUS_OM_INTERFACE,
					  "InterfacesRemoved") &&
256
		   dbus_message_has_signature(message, InterfacesRemovedType::as_string)) {
Max Kellermann's avatar
Max Kellermann committed
257 258 259 260 261 262 263 264 265
		Remove(ReadMessageIter(*message).GetString());
		return DBUS_HANDLER_RESULT_HANDLED;
	} else
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

static std::unique_ptr<NeighborExplorer>
udisks_neighbor_create(EventLoop &event_loop,
		     NeighborListener &listener,
Rosen Penev's avatar
Rosen Penev committed
266
		     [[maybe_unused]] const ConfigBlock &block)
Max Kellermann's avatar
Max Kellermann committed
267 268 269 270 271 272 273 274
{
	return std::make_unique<UdisksNeighborExplorer>(event_loop, listener);
}

const NeighborPlugin udisks_neighbor_plugin = {
	"udisks",
	udisks_neighbor_create,
};