diff --git a/CMakeLists.txt b/CMakeLists.txt index 6da158efd..4bc74eb24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -254,6 +254,10 @@ optional_component(DEVICEKIT ON "Devices: DeviceKit backend" DEPENDS "D-Bus support" HAVE_DBUS ) +optional_component(UDISKS2 ON "Devices: UDisks2 backend" + DEPENDS "D-Bus support" HAVE_DBUS +) + optional_component(SPOTIFY_BLOB ON "Spotify support: non-GPL binary helper" DEPENDS "protobuf" PROTOBUF_FOUND PROTOBUF_PROTOC_EXECUTABLE DEPENDS "libspotify" SPOTIFY_FOUND diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4f0832b5b..ca6625abf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -964,6 +964,29 @@ if(HAVE_DBUS) dbus/udisksdevice) endif(HAVE_DEVICEKIT) + if(HAVE_UDISKS2) + set_source_files_properties(dbus/org.freedesktop.DBus.ObjectManager.xml + PROPERTIES NO_NAMESPACE dbus/objectmanager INCLUDE dbus/metatypes.h) + set_source_files_properties(dbus/org.freedesktop.UDisks2.Filesystem.xml + PROPERTIES NO_NAMESPACE dbus/udisks2filesystem INCLUDE dbus/metatypes.h) + set_source_files_properties(dbus/org.freedesktop.UDisks2.Block.xml + PROPERTIES NO_NAMESPACE dbus/udisks2block INCLUDE dbus/metatypes.h) + set_source_files_properties(dbus/org.freedesktop.UDisks2.Drive.xml + PROPERTIES NO_NAMESPACE dbus/udisks2drive INCLUDE dbus/metatypes.h) + qt4_add_dbus_interface(SOURCES + dbus/org.freedesktop.DBus.ObjectManager.xml + dbus/objectmanager) + qt4_add_dbus_interface(SOURCES + dbus/org.freedesktop.UDisks2.Filesystem.xml + dbus/udisks2filesystem) + qt4_add_dbus_interface(SOURCES + dbus/org.freedesktop.UDisks2.Block.xml + dbus/udisks2block) + qt4_add_dbus_interface(SOURCES + dbus/org.freedesktop.UDisks2.Drive.xml + dbus/udisks2drive) + endif(HAVE_UDISKS2) + # Wiimotedev interface classes if(ENABLE_WIIMOTEDEV) qt4_add_dbus_interface(SOURCES @@ -999,6 +1022,11 @@ optional_source(HAVE_DEVICEKIT HEADERS devices/devicekitlister.h ) +optional_source(HAVE_UDISKS2 + SOURCES devices/udisks2lister.cpp + HEADERS devices/udisks2lister.h +) + # Libgpod device backend optional_source(HAVE_LIBGPOD INCLUDE_DIRECTORIES ${LIBGPOD_INCLUDE_DIRS} diff --git a/src/config.h.in b/src/config.h.in index f22fe9bc8..2bd31b705 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -41,6 +41,7 @@ #cmakedefine HAVE_SKYDRIVE #cmakedefine HAVE_SPARKLE #cmakedefine HAVE_SPOTIFY_DOWNLOADER +#cmakedefine HAVE_UDISKS2 #cmakedefine HAVE_VK #cmakedefine HAVE_WIIMOTEDEV #cmakedefine TAGLIB_HAS_OPUS diff --git a/src/core/metatypes.cpp b/src/core/metatypes.cpp index 62e246012..00b3553d9 100644 --- a/src/core/metatypes.cpp +++ b/src/core/metatypes.cpp @@ -125,5 +125,8 @@ void RegisterMetaTypes() { qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); + + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); #endif } diff --git a/src/dbus/metatypes.h b/src/dbus/metatypes.h index 4e0024574..83bc5e615 100644 --- a/src/dbus/metatypes.h +++ b/src/dbus/metatypes.h @@ -20,6 +20,12 @@ #include -Q_DECLARE_METATYPE(QList); +Q_DECLARE_METATYPE(QList) + +typedef QMap InterfacesAndProperties; +typedef QMap ManagedObjectList; + +Q_DECLARE_METATYPE(InterfacesAndProperties) +Q_DECLARE_METATYPE(ManagedObjectList) #endif // DBUS_METATYPES_H_ diff --git a/src/dbus/org.freedesktop.DBus.ObjectManager.xml b/src/dbus/org.freedesktop.DBus.ObjectManager.xml new file mode 100644 index 000000000..efc389dd2 --- /dev/null +++ b/src/dbus/org.freedesktop.DBus.ObjectManager.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/dbus/org.freedesktop.UDisks2.Block.xml b/src/dbus/org.freedesktop.UDisks2.Block.xml new file mode 100644 index 000000000..f0e3a06c4 --- /dev/null +++ b/src/dbus/org.freedesktop.UDisks2.Block.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/dbus/org.freedesktop.UDisks2.Drive.xml b/src/dbus/org.freedesktop.UDisks2.Drive.xml new file mode 100644 index 000000000..5312b2250 --- /dev/null +++ b/src/dbus/org.freedesktop.UDisks2.Drive.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/dbus/org.freedesktop.UDisks2.Filesystem.xml b/src/dbus/org.freedesktop.UDisks2.Filesystem.xml new file mode 100644 index 000000000..1781919ab --- /dev/null +++ b/src/dbus/org.freedesktop.UDisks2.Filesystem.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/devices/devicemanager.cpp b/src/devices/devicemanager.cpp index 6396b56cb..32ba33657 100644 --- a/src/devices/devicemanager.cpp +++ b/src/devices/devicemanager.cpp @@ -59,6 +59,9 @@ #ifdef HAVE_LIBMTP #include "mtpdevice.h" #endif +#ifdef HAVE_UDISKS2 +#include "udisks2lister.h" +#endif using std::bind; @@ -191,6 +194,9 @@ DeviceManager::DeviceManager(Application* app, QObject* parent) #ifdef HAVE_DEVICEKIT AddLister(new DeviceKitLister); #endif +#ifdef HAVE_UDISKS2 + AddLister(new Udisks2Lister); +#endif #ifdef HAVE_GIO AddLister(new GioLister); #endif @@ -228,7 +234,10 @@ void DeviceManager::LoadAllDevices() { for (const DeviceDatabaseBackend::Device& device : devices) { DeviceInfo info; info.InitFromDb(device); + + beginInsertRows(QModelIndex(), devices_.count(), devices_.count()); devices_ << info; + endInsertRows(); } } diff --git a/src/devices/udisks2lister.cpp b/src/devices/udisks2lister.cpp new file mode 100644 index 000000000..99251468a --- /dev/null +++ b/src/devices/udisks2lister.cpp @@ -0,0 +1,286 @@ +#include "udisks2lister.h" + +#include + +#include "core/logging.h" +#include "core/utilities.h" + +#include "dbus/udisks2filesystem.h" +#include "dbus/udisks2block.h" +#include "dbus/udisks2drive.h" + +const QString Udisks2Lister::udisks2service_ = "org.freedesktop.UDisks2"; + +Udisks2Lister::Udisks2Lister() { + +} + +Udisks2Lister::~Udisks2Lister() { + qLog(Debug) << __PRETTY_FUNCTION__; +} + +QStringList Udisks2Lister::DeviceUniqueIDs() { + QReadLocker locker(&device_data_lock_); + return device_data_.keys(); +} + +QVariantList Udisks2Lister::DeviceIcons(const QString &id) { + return QVariantList(); +} + +QString Udisks2Lister::DeviceManufacturer(const QString &id) { + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) + return ""; + return device_data_[id].vendor; +} + +QString Udisks2Lister::DeviceModel(const QString &id) { + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) + return ""; + return device_data_[id].model; +} + +quint64 Udisks2Lister::DeviceCapacity(const QString &id) { + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) + return 0; + return device_data_[id].capacity; +} + +quint64 Udisks2Lister::DeviceFreeSpace(const QString &id) { + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) + return 0; + return device_data_[id].free_space; +} + +QVariantMap Udisks2Lister::DeviceHardwareInfo(const QString &id) { + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) + return QVariantMap(); + + QVariantMap result; + + const auto &data = device_data_[id]; + result[QT_TR_NOOP("DBus path")] = data.dbus_path; + result[QT_TR_NOOP("Serial number")] = data.serial; + result[QT_TR_NOOP("Mount points")] = data.mount_paths.join(", "); + result[QT_TR_NOOP("Parition label")] = data.label; + result[QT_TR_NOOP("UUID")] = data.uuid; + + return result; +} + +QString Udisks2Lister::MakeFriendlyName(const QString &id) { + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) + return ""; + return device_data_[id].friendly_name; +} + +QList Udisks2Lister::MakeDeviceUrls(const QString &id) { + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) + return QList(); + return QList() << QUrl::fromLocalFile(device_data_[id].mount_paths.at(0)); +} + +void Udisks2Lister::UnmountDevice(const QString &id) { + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) + return; + + OrgFreedesktopUDisks2FilesystemInterface filesystem( + udisks2service_, + device_data_[id].dbus_path, + QDBusConnection::systemBus()); + + if (filesystem.isValid()) + { + auto umountResult = filesystem.Unmount(QVariantMap()); + umountResult.waitForFinished(); + + OrgFreedesktopUDisks2DriveInterface drive( + udisks2service_, + device_data_[id].dbus_drive_path, + QDBusConnection::systemBus()); + + if (drive.isValid()) + { + auto ejectResult = drive.Eject(QVariantMap()); + ejectResult.waitForFinished(); + } + } +} + +void Udisks2Lister::UpdateDeviceFreeSpace(const QString &id) { + QWriteLocker locker(&device_data_lock_); + device_data_[id].free_space = Utilities::FileSystemFreeSpace(device_data_[id].mount_paths.at(0)); + + emit DeviceChanged(id); +} + +void Udisks2Lister::Init() { + udisks2_interface_.reset(new OrgFreedesktopDBusObjectManagerInterface( + udisks2service_, + "/org/freedesktop/UDisks2", + QDBusConnection::systemBus())); + + QDBusPendingReply reply = udisks2_interface_->GetManagedObjects(); + reply.waitForFinished(); + + if (!reply.isValid()) { + qLog(Warning) << "Error enumerating udisks2 devices:" + << reply.error().name() << reply.error().message(); + udisks2_interface_.reset(); + return; + } + + for (const QDBusObjectPath &path : reply.value().keys()) { + auto partitionData = ReadPartitionData(path, false); + + if (!partitionData.dbus_path.isEmpty()) + { + QWriteLocker locker(&device_data_lock_); + device_data_[partitionData.unique_id()] = partitionData; + } + } + + for (const auto &id : device_data_.keys()) { + emit DeviceAdded(id); + } + + connect(udisks2_interface_.get(), SIGNAL(InterfacesAdded(QDBusObjectPath, InterfacesAndProperties)), + SLOT(DBusInterfaceAdded(QDBusObjectPath, InterfacesAndProperties))); + connect(udisks2_interface_.get(), SIGNAL(InterfacesRemoved(QDBusObjectPath, QStringList)), + SLOT(DBusInterfaceRemoved(QDBusObjectPath, QStringList))); +} + +void Udisks2Lister::DBusInterfaceAdded(const QDBusObjectPath &path, + const InterfacesAndProperties &interfaces) { + // FIXME handle unmount jobs too + for (auto interface = interfaces.constBegin(); interface != interfaces.constEnd(); ++interface) + { + if (interface.key() != "org.freedesktop.UDisks2.Job" + || interface.value()["Operation"] != "filesystem-mount") + continue; + + const QDBusArgument &objects = interface.value()["Objects"].value(); + + QList mountedParititons; + objects.beginArray(); + while (!objects.atEnd()) { + QDBusObjectPath extractedPath; + objects >> extractedPath; + mountedParititons.push_back(extractedPath); + } + objects.endArray(); + + qLog(Debug) << "Udisks2 something mounted: " << mountedParititons.at(0).path(); + + { + QMutexLocker locker(&jobs_lock_); + mounting_jobs_[path.path()] = mountedParititons; + } + } +} + +void Udisks2Lister::DBusInterfaceRemoved(const QDBusObjectPath &path, const QStringList &ifaces) { + if (!isPendingJob(path)) + RemoveDevice(path); +} + +bool Udisks2Lister::isPendingJob(const QDBusObjectPath &path) +{ + // should be actually done with a succcess signal from job, I guess, but it makes it kinda complicated + QMutexLocker locker(&jobs_lock_); + + if (!mounting_jobs_.contains(path.path())) + return false; + + const auto &mountpaths = mounting_jobs_[path.path()]; + for (const auto &partition : mountpaths) { + auto data = ReadPartitionData(partition, true); + if (!data.dbus_path.isEmpty()) { + QWriteLocker locker(&device_data_lock_); + device_data_[data.unique_id()] = data; + DeviceAdded(data.unique_id()); + } + } + mounting_jobs_.remove(path.path()); + return true; +} + +void Udisks2Lister::RemoveDevice(const QDBusObjectPath &path) +{ + QWriteLocker locker(&device_data_lock_); + QString id; + for (const auto &data : device_data_) { + if (data.dbus_path == path.path()) + id = data.unique_id(); + } + + if (id.isEmpty()) + return; + + device_data_.remove(id); + DeviceRemoved(id); +} + +Udisks2Lister::PartitionData Udisks2Lister::ReadPartitionData(const QDBusObjectPath &path, + bool beingMounted) { + PartitionData result; + OrgFreedesktopUDisks2FilesystemInterface filesystem( + udisks2service_, + path.path(), + QDBusConnection::systemBus()); + OrgFreedesktopUDisks2BlockInterface block( + udisks2service_, + path.path(), + QDBusConnection::systemBus()); + + if (filesystem.isValid() + && block.isValid() + && (beingMounted || !filesystem.mountPoints().empty())) { + + OrgFreedesktopUDisks2DriveInterface drive( + udisks2service_, + block.drive().path(), + QDBusConnection::systemBus()); + + if (drive.isValid() + && drive.mediaRemovable()) { + result.dbus_path = path.path(); + result.dbus_drive_path = block.drive().path(); + + result.serial = drive.serial(); + result.vendor = drive.vendor(); + result.model = drive.model(); + + result.label = block.idLabel(); + result.uuid = block.idUUID(); + result.capacity = drive.size(); + + if (!result.label.isEmpty()) + result.friendly_name = result.label; + else + result.friendly_name = result.model + " " + result.uuid; + + for (const auto &path : filesystem.mountPoints()) + result.mount_paths.push_back(path); + + result.free_space = Utilities::FileSystemFreeSpace(result.mount_paths.at(0)); + } + } + + return result; +} + +QString Udisks2Lister::PartitionData::unique_id() const { + return QString("Udisks2/%1/%2/%3/%4/%5") + .arg(serial, vendor, model) + .arg(capacity) + .arg(uuid); +} diff --git a/src/devices/udisks2lister.h b/src/devices/udisks2lister.h new file mode 100644 index 000000000..639db404b --- /dev/null +++ b/src/devices/udisks2lister.h @@ -0,0 +1,77 @@ +#pragma once + +#include + +#include "devicelister.h" + +#include "dbus/objectmanager.h" + +class Udisks2Lister : public DeviceLister { + Q_OBJECT + +public: + Udisks2Lister(); + ~Udisks2Lister(); + + QStringList DeviceUniqueIDs(); + QVariantList DeviceIcons(const QString &id); + QString DeviceManufacturer(const QString &id); + QString DeviceModel(const QString &id); + quint64 DeviceCapacity(const QString &id); + quint64 DeviceFreeSpace(const QString &id); + QVariantMap DeviceHardwareInfo(const QString &id); + + QString MakeFriendlyName(const QString &id); + QList MakeDeviceUrls(const QString &id); + + void UnmountDevice(const QString &id); + +public slots: + void UpdateDeviceFreeSpace(const QString &id); + +protected: + void Init(); + +private slots: + void DBusInterfaceAdded(const QDBusObjectPath &path, const InterfacesAndProperties &ifaces); + void DBusInterfaceRemoved(const QDBusObjectPath &path, const QStringList &ifaces); + +private: + bool isPendingJob(const QDBusObjectPath &path); + void RemoveDevice(const QDBusObjectPath &path); + + QMutex jobs_lock_; + QMap> mounting_jobs_; + +private: + class PartitionData { + public: + QString unique_id() const; + + QString dbus_path; + QString friendly_name; + + // Device + QString serial; + QString vendor; + QString model; + quint64 capacity = 0; + QString dbus_drive_path; + + // Paritition + QString label; + QString uuid; + quint64 free_space = 0; + QStringList mount_paths; + }; + + PartitionData ReadPartitionData(const QDBusObjectPath &path, bool beingMounted); + + QReadWriteLock device_data_lock_; + QMap device_data_; + +private: + std::unique_ptr udisks2_interface_; + + static const QString udisks2service_; +};