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

#include <stdexcept>
#include <string>
#include <list>
#include <map>

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

class UdisksNeighborExplorer final
51
	: public NeighborExplorer {
Max Kellermann's avatar
Max Kellermann committed
52

53 54
	EventLoop &event_loop;

55
	Manual<SafeSingleton<ODBus::Glue>> dbus_glue;
Max Kellermann's avatar
Max Kellermann committed
56

57
	ODBus::AsyncRequest list_request;
Max Kellermann's avatar
Max Kellermann committed
58 59 60 61 62 63 64 65 66 67 68

	/**
	 * 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:
69
	UdisksNeighborExplorer(EventLoop &_event_loop,
Max Kellermann's avatar
Max Kellermann committed
70
			       NeighborListener &_listener) noexcept
71
		:NeighborExplorer(_listener), event_loop(_event_loop) {}
Max Kellermann's avatar
Max Kellermann committed
72 73

	auto &GetEventLoop() noexcept {
74 75 76 77
		return event_loop;
	}

	auto &&GetConnection() noexcept {
78
		return dbus_glue.Get()->GetConnection();
Max Kellermann's avatar
Max Kellermann committed
79 80 81 82 83 84 85 86
	}

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

private:
87 88 89
	void DoOpen();
	void DoClose() noexcept;

90
	void Insert(UDisks2::Object &&o) noexcept;
Max Kellermann's avatar
Max Kellermann committed
91 92
	void Remove(const std::string &path) noexcept;

93
	void OnListNotify(ODBus::Message reply) noexcept;
Max Kellermann's avatar
Max Kellermann committed
94 95 96 97 98 99 100 101

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

102 103
inline void
UdisksNeighborExplorer::DoOpen()
Max Kellermann's avatar
Max Kellermann committed
104 105 106
{
	using namespace ODBus;

107
	dbus_glue.Construct(event_loop);
Max Kellermann's avatar
Max Kellermann committed
108

109
	auto &connection = GetConnection();
Max Kellermann's avatar
Max Kellermann committed
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127

	try {
		Error error;
		dbus_bus_add_match(connection,
				   "type='signal',sender='" UDISKS2_INTERFACE "',"
				   "interface='" DBUS_OM_INTERFACE "',"
				   "path='" UDISKS2_PATH "'",
				   error);
		error.CheckThrow("DBus AddMatch error");

		dbus_connection_add_filter(connection,
					   HandleMessage, this,
					   nullptr);

		auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
						  UDISKS2_PATH,
						  DBUS_OM_INTERFACE,
						  "GetManagedObjects");
128 129 130
		list_request.Send(connection, *msg.Get(),
				  std::bind(&UdisksNeighborExplorer::OnListNotify,
					    this, std::placeholders::_1));
Max Kellermann's avatar
Max Kellermann committed
131
	} catch (...) {
132
		dbus_glue.Destruct();
Max Kellermann's avatar
Max Kellermann committed
133 134 135 136 137
		throw;
	}
}

void
138
UdisksNeighborExplorer::Open()
Max Kellermann's avatar
Max Kellermann committed
139
{
140 141
	BlockingCall(GetEventLoop(), [this](){ DoOpen(); });
}
Max Kellermann's avatar
Max Kellermann committed
142

143 144 145
inline void
UdisksNeighborExplorer::DoClose() noexcept
{
146 147
	if (list_request) {
		list_request.Cancel();
Max Kellermann's avatar
Max Kellermann committed
148 149 150 151 152
	}

	// TODO: remove_match
	// TODO: remove_filter

153
	dbus_glue.Destruct();
Max Kellermann's avatar
Max Kellermann committed
154 155
}

156 157 158 159 160 161
void
UdisksNeighborExplorer::Close() noexcept
{
	BlockingCall(GetEventLoop(), [this](){ DoClose(); });
}

Max Kellermann's avatar
Max Kellermann committed
162 163 164 165 166 167 168 169 170 171 172 173 174
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
175
UdisksNeighborExplorer::Insert(UDisks2::Object &&o) noexcept
Max Kellermann's avatar
Max Kellermann committed
176 177 178
{
	assert(o.IsValid());

179
	const NeighborInfo info = ToNeighborInfo(o);
Max Kellermann's avatar
Max Kellermann committed
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212

	{
		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
213
UdisksNeighborExplorer::OnListNotify(ODBus::Message reply) noexcept
Max Kellermann's avatar
Max Kellermann committed
214
{
215 216 217 218
	try{
		ParseObjects(reply,
			     std::bind(&UdisksNeighborExplorer::Insert,
				       this, std::placeholders::_1));
Max Kellermann's avatar
Max Kellermann committed
219
	} catch (...) {
220 221
		LogError(std::current_exception(),
			 "Failed to parse GetManagedObjects reply");
Max Kellermann's avatar
Max Kellermann committed
222 223 224 225 226 227 228 229 230 231 232
		return;
	}
}

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

	if (dbus_message_is_signal(message, DBUS_OM_INTERFACE,
				   "InterfacesAdded") &&
233
	    dbus_message_has_signature(message, InterfacesAddedType::value)) {
234
		RecurseInterfaceDictEntry(ReadMessageIter(*message), [this](const char *path, auto &&i){
235
				UDisks2::Object o(path);
236 237
				UDisks2::ParseObject(o, std::move(i));
				if (o.IsValid())
238
					this->Insert(std::move(o));
239
			});
Max Kellermann's avatar
Max Kellermann committed
240 241 242 243

		return DBUS_HANDLER_RESULT_HANDLED;
	} else if (dbus_message_is_signal(message, DBUS_OM_INTERFACE,
					  "InterfacesRemoved") &&
244
		   dbus_message_has_signature(message, InterfacesRemovedType::value)) {
Max Kellermann's avatar
Max Kellermann committed
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
		Remove(ReadMessageIter(*message).GetString());
		return DBUS_HANDLER_RESULT_HANDLED;
	} else
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

DBusHandlerResult
UdisksNeighborExplorer::HandleMessage(DBusConnection *connection,
				      DBusMessage *message,
				      void *user_data) noexcept
{
	auto &agent = *(UdisksNeighborExplorer *)user_data;

	return agent.HandleMessage(connection, message);
}

static std::unique_ptr<NeighborExplorer>
udisks_neighbor_create(EventLoop &event_loop,
		     NeighborListener &listener,
		     gcc_unused const ConfigBlock &block)
{
	return std::make_unique<UdisksNeighborExplorer>(event_loop, listener);
}

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