diff options
author | Max Kellermann <max@musicpd.org> | 2018-05-31 10:52:36 +0200 |
---|---|---|
committer | Max Kellermann <max@musicpd.org> | 2018-06-02 19:44:46 +0200 |
commit | 3aade670462da2e44a303b80950051627dd3f29d (patch) | |
tree | b3219e147207fd384afbc3cef9857dd64bd44839 /src/neighbor | |
parent | dc111bbec2b7cc500c155e48dffe1775c90b8e19 (diff) |
basic udisks2 support
To get udisks2 support started, this commit contains the configure.ac
option and a "neighbor" plugin which shows block devices. Later, this
will allow mounting removable media with a new storage plugin.
Diffstat (limited to 'src/neighbor')
-rw-r--r-- | src/neighbor/Registry.cxx | 4 | ||||
-rw-r--r-- | src/neighbor/plugins/UdisksNeighborPlugin.cxx | 408 | ||||
-rw-r--r-- | src/neighbor/plugins/UdisksNeighborPlugin.hxx | 27 |
3 files changed, 439 insertions, 0 deletions
diff --git a/src/neighbor/Registry.cxx b/src/neighbor/Registry.cxx index fad045f27..99943f750 100644 --- a/src/neighbor/Registry.cxx +++ b/src/neighbor/Registry.cxx @@ -22,6 +22,7 @@ #include "NeighborPlugin.hxx" #include "plugins/SmbclientNeighborPlugin.hxx" #include "plugins/UpnpNeighborPlugin.hxx" +#include "plugins/UdisksNeighborPlugin.hxx" #include <string.h> @@ -32,6 +33,9 @@ const NeighborPlugin *const neighbor_plugins[] = { #ifdef ENABLE_UPNP &upnp_neighbor_plugin, #endif +#ifdef ENABLE_UDISKS + &udisks_neighbor_plugin, +#endif nullptr }; diff --git a/src/neighbor/plugins/UdisksNeighborPlugin.cxx b/src/neighbor/plugins/UdisksNeighborPlugin.cxx new file mode 100644 index 000000000..602582545 --- /dev/null +++ b/src/neighbor/plugins/UdisksNeighborPlugin.cxx @@ -0,0 +1,408 @@ +/* + * 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 "config.h" +#include "UdisksNeighborPlugin.hxx" +#include "lib/dbus/Connection.hxx" +#include "lib/dbus/Error.hxx" +#include "lib/dbus/Watch.hxx" +#include "lib/dbus/Message.hxx" +#include "lib/dbus/PendingCall.hxx" +#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" +#include "thread/Mutex.hxx" +#include "util/Domain.hxx" +#include "util/StringAPI.hxx" +#include "Log.hxx" + +#include <stdexcept> +#include <string> +#include <list> +#include <map> + +static constexpr Domain udisks_domain("udisks"); + +struct UdisksObject { + std::string path; + + std::string drive_id, block_id; + + bool is_filesystem = false; + + bool IsValid() const noexcept { + return !drive_id.empty() || !block_id.empty(); + } + + std::string GetUri() const noexcept { + if (!drive_id.empty()) + return "udisks://" + drive_id; + else if (!block_id.empty()) + return "udisks://" + block_id; + else + return {}; + } + + NeighborInfo ToNeighborInfo() const noexcept { + return {GetUri(), path}; + } +}; + +class UdisksNeighborExplorer final + : public NeighborExplorer, ODBus::WatchManagerObserver { + + ODBus::WatchManager dbus_watch; + + ODBus::PendingCall pending_list_call; + + /** + * 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: + UdisksNeighborExplorer(EventLoop &event_loop, + NeighborListener &_listener) noexcept + :NeighborExplorer(_listener), + dbus_watch(event_loop, *this) {} + + auto &GetEventLoop() noexcept { + return dbus_watch.GetEventLoop(); + } + + /* virtual methods from class NeighborExplorer */ + void Open() override; + void Close() noexcept override; + List GetList() const noexcept override; + +private: + /* virtual methods from class ODBus::WatchManagerObserver */ + void OnDBusClosed() noexcept override; + +private: + void Insert(UdisksObject &&o) noexcept; + void Remove(const std::string &path) noexcept; + + void OnListNotify(DBusPendingCall *pending) noexcept; + + static void OnListNotify(DBusPendingCall *pending, + void *user_data) noexcept { + auto &e = *(UdisksNeighborExplorer *)user_data; + e.OnListNotify(pending); + } + + DBusHandlerResult HandleMessage(DBusConnection *dbus_connection, + DBusMessage *message) noexcept; + static DBusHandlerResult HandleMessage(DBusConnection *dbus_connection, + DBusMessage *message, + void *user_data) noexcept; +}; + +void +UdisksNeighborExplorer::Open() +{ + using namespace ODBus; + + dbus_watch.SetConnection(Connection::GetSystem()); + + auto &connection = dbus_watch.GetConnection(); + dbus_connection_set_exit_on_disconnect(connection, false); + + 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"); + pending_list_call = PendingCall::SendWithReply(connection, msg.Get()); + pending_list_call.SetNotify(OnListNotify, this); + } catch (...) { + dbus_watch.SetConnection(Connection()); + throw; + } +} + +void +UdisksNeighborExplorer::Close() noexcept +{ + using namespace ODBus; + + if (pending_list_call) { + pending_list_call.Cancel(); + } + + // TODO: remove_match + // TODO: remove_filter + + dbus_watch.SetConnection(Connection()); +} + +template<typename I> +gcc_pure +static const char * +CheckString(I &&i) noexcept +{ + if (i.GetArgType() != DBUS_TYPE_STRING) + return nullptr; + + return i.GetString(); +} + +template<typename I> +gcc_pure +static const char * +CheckVariantString(I &&i) noexcept +{ + if (i.GetArgType() != DBUS_TYPE_VARIANT) + return nullptr; + + return CheckString(i.Recurse()); +} + +static void +ParseDriveDictEntry(UdisksObject &o, ODBus::ReadMessageIter &&i) noexcept +{ + if (i.GetArgType() != DBUS_TYPE_STRING) + return; + + const char *name = i.GetString(); + i.Next(); + + if (StringIsEqual(name, "Id")) { + const char *value = CheckVariantString(i); + if (value != nullptr && o.drive_id.empty()) + o.drive_id = value; + } +} + +static void +ParseBlockDictEntry(UdisksObject &o, ODBus::ReadMessageIter &&i) noexcept +{ + if (i.GetArgType() != DBUS_TYPE_STRING) + return; + + const char *name = i.GetString(); + i.Next(); + + if (StringIsEqual(name, "Id")) { + const char *value = CheckVariantString(i); + if (value != nullptr && o.block_id.empty()) + o.block_id = value; + } +} + +static void +ParseInterface(UdisksObject &o, const char *interface, + ODBus::ReadMessageIter &&i) noexcept +{ + if (StringIsEqual(interface, "org.freedesktop.UDisks2.Drive")) { + for (; i.GetArgType() == DBUS_TYPE_DICT_ENTRY; i.Next()) + ParseDriveDictEntry(o, i.Recurse()); + } else if (StringIsEqual(interface, "org.freedesktop.UDisks2.Block")) { + for (; i.GetArgType() == DBUS_TYPE_DICT_ENTRY; i.Next()) + ParseBlockDictEntry(o, i.Recurse()); + } else if (StringIsEqual(interface, "org.freedesktop.UDisks2.Filesystem")) { + o.is_filesystem = true; + } +} + +static void +ParseInterfaceDictEntry(UdisksObject &o, ODBus::ReadMessageIter &&i) noexcept +{ + if (i.GetArgType() != DBUS_TYPE_STRING) + return; + + const char *interface = i.GetString(); + i.Next(); + + if (i.GetArgType() != DBUS_TYPE_ARRAY) + return; + + ParseInterface(o, interface, i.Recurse()); +} + +static bool +ParseObject(UdisksObject &o, ODBus::ReadMessageIter &&i) noexcept +{ + if (i.GetArgType() != DBUS_TYPE_OBJECT_PATH) + return false; + + o.path = i.GetString(); + + i.Next(); + + if (i.GetArgType() != DBUS_TYPE_ARRAY) + return false; + + i.Recurse().ForEach(DBUS_TYPE_DICT_ENTRY, [&o](auto &&j){ + ParseInterfaceDictEntry(o, j.Recurse()); + }); + + return true; +} + +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 +UdisksNeighborExplorer::OnDBusClosed() noexcept +{ + // TODO: reconnect +} + +void +UdisksNeighborExplorer::Insert(UdisksObject &&o) noexcept +{ + assert(o.IsValid()); + + const NeighborInfo info = o.ToNeighborInfo(); + + { + 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 +UdisksNeighborExplorer::OnListNotify(DBusPendingCall *pending) noexcept +{ + assert(pending == pending_list_call.Get()); + + pending_list_call = {}; + + using namespace ODBus; + Message reply = Message::StealReply(*pending); + + try { + reply.CheckThrowError(); + } catch (...) { + LogError(std::current_exception()); + return; + } + + ReadMessageIter i(*reply.Get()); + if (i.GetArgType() != DBUS_TYPE_ARRAY) { + LogError(udisks_domain, "Malformed response"); + return; + } + + i.Recurse().ForEach(DBUS_TYPE_DICT_ENTRY, [this](auto &&j){ + UdisksObject o; + if (ParseObject(o, j.Recurse()) && o.IsValid()) + Insert(std::move(o)); + }); +} + +inline DBusHandlerResult +UdisksNeighborExplorer::HandleMessage(DBusConnection *, DBusMessage *message) noexcept +{ + using namespace ODBus; + + if (dbus_message_is_signal(message, DBUS_OM_INTERFACE, + "InterfacesAdded") && + dbus_message_has_signature(message, DBUS_OM_INTERFACES_ADDED_SIGNATURE)) { + UdisksObject o; + if (ParseObject(o, ReadMessageIter(*message)) && o.IsValid()) + Insert(std::move(o)); + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal(message, DBUS_OM_INTERFACE, + "InterfacesRemoved") && + dbus_message_has_signature(message, DBUS_OM_INTERFACES_REMOVED_SIGNATURE)) { + 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, +}; diff --git a/src/neighbor/plugins/UdisksNeighborPlugin.hxx b/src/neighbor/plugins/UdisksNeighborPlugin.hxx new file mode 100644 index 000000000..f7b122b15 --- /dev/null +++ b/src/neighbor/plugins/UdisksNeighborPlugin.hxx @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#ifndef MPD_NEIGHBOR_UDISKS_HXX +#define MPD_NEIGHBOR_UDISKS_HXX + +struct NeighborPlugin; + +extern const NeighborPlugin udisks_neighbor_plugin; + +#endif |