diff --git a/src/solid/devices/backends/udisks2/udisks2.h b/src/solid/devices/backends/udisks2/udisks2.h --- a/src/solid/devices/backends/udisks2/udisks2.h +++ b/src/solid/devices/backends/udisks2/udisks2.h @@ -45,10 +45,12 @@ #define UD2_UDI_DISKS_PREFIX "/org/freedesktop/UDisks2" #define UD2_DBUS_PATH_MANAGER "/org/freedesktop/UDisks2/Manager" #define UD2_DBUS_PATH_DRIVES "/org/freedesktop/UDisks2/drives/" +#define UD2_DBUS_PATH_BLOCK_DEVICES "/org/freedesktop/UDisks2/block_devices" #define UD2_DBUS_PATH_JOBS "/org/freedesktop/UDisks2/jobs/" #define DBUS_INTERFACE_PROPS "org.freedesktop.DBus.Properties" #define DBUS_INTERFACE_INTROSPECT "org.freedesktop.DBus.Introspectable" #define DBUS_INTERFACE_MANAGER "org.freedesktop.DBus.ObjectManager" +#define UD2_DBUS_INTERFACE_PREFIX "org.freedesktop.UDisks2" #define UD2_DBUS_INTERFACE_BLOCK "org.freedesktop.UDisks2.Block" #define UD2_DBUS_INTERFACE_DRIVE "org.freedesktop.UDisks2.Drive" #define UD2_DBUS_INTERFACE_PARTITION "org.freedesktop.UDisks2.Partition" diff --git a/src/solid/devices/backends/udisks2/udisksblock.cpp b/src/solid/devices/backends/udisks2/udisksblock.cpp --- a/src/solid/devices/backends/udisks2/udisksblock.cpp +++ b/src/solid/devices/backends/udisks2/udisksblock.cpp @@ -19,6 +19,8 @@ */ #include "udisksblock.h" +#include "udisksdevicebackend.h" +#include "udisksmanager.h" #if defined(Q_OS_LINUX) #include @@ -33,7 +35,6 @@ #include #include #include -#include #include "udisks_debug.h" @@ -47,31 +48,25 @@ // we have a drive (non-block device for udisks), so let's find the corresponding (real) block device if (m_devNum == 0 || m_devFile.isEmpty()) { - const QString path = "/org/freedesktop/UDisks2/block_devices"; - QDBusMessage call = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, path, - DBUS_INTERFACE_INTROSPECT, "Introspect"); - QDBusPendingReply reply = QDBusConnection::systemBus().asyncCall(call); - reply.waitForFinished(); - - if (reply.isValid()) { - QDomDocument dom; - dom.setContent(reply.value()); - QDomNodeList nodeList = dom.documentElement().elementsByTagName("node"); - for (int i = 0; i < nodeList.count(); i++) { - QDomElement nodeElem = nodeList.item(i).toElement(); - if (!nodeElem.isNull() && nodeElem.hasAttribute("name")) { - const QString udi = path + "/" + nodeElem.attribute("name"); - - Device device(udi); - if (device.drivePath() == dev->udi()) { - m_devNum = device.prop("DeviceNumber").toULongLong(); - m_devFile = QFile::decodeName(device.prop("Device").toByteArray()); - break; - } - } + // kinda breaks the encapsulation if this thing ends up calling all the way into the Manager... + const auto allProperties = DeviceBackend::manager()->allProperties(); + for (auto it = allProperties.begin(), end = allProperties.end(); it != end; ++it) { + const QVariantMap blockProps = it->value(UD2_DBUS_INTERFACE_BLOCK); + + // FIXME the old code introspecting DBus found "/dev/nvme0n1p3" for my SSD + // whereas the new code finds "/dev/nvme0n1" which looks more sensible + // but this could have some unwanted implications. + // Not really sure what this code is trying to achieve anyway, but I think + // returning the drive device instead of a (random?) partition on it is better + + const QString drivePath = blockProps.value(QStringLiteral("Drive")).value().path(); + if (drivePath != dev->udi()) { + continue; } - } else { - qCWarning(UDISKS2) << "Failed enumerating UDisks2 objects:" << reply.error().name() << "\n" << reply.error().message(); + + m_devNum = blockProps.value(QStringLiteral("DeviceNumber")).toULongLong(); + m_devFile = QFile::decodeName(blockProps.value(QStringLiteral("Device")).toByteArray()); + break; } } diff --git a/src/solid/devices/backends/udisks2/udisksdevice.cpp b/src/solid/devices/backends/udisks2/udisksdevice.cpp --- a/src/solid/devices/backends/udisks2/udisksdevice.cpp +++ b/src/solid/devices/backends/udisks2/udisksdevice.cpp @@ -42,8 +42,6 @@ #include #include -#include - using namespace Solid::Backends::UDisks2; // Adapted from KLocale as Solid needs to be Qt-only @@ -93,8 +91,8 @@ , m_backend(DeviceBackend::backendForUDI(udi)) { if (m_backend) { - connect(m_backend, SIGNAL(changed()), this, SIGNAL(changed())); - connect(m_backend, SIGNAL(propertyChanged(QMap)), this, SIGNAL(propertyChanged(QMap))); + connect(m_backend, &DeviceBackend::changed, this, &Device::changed); + connect(m_backend, &DeviceBackend::propertyChanged, this, &Device::propertyChanged); } else { qCDebug(UDISKS2) << "Created invalid Device for udi" << udi; } diff --git a/src/solid/devices/backends/udisks2/udisksdevicebackend.h b/src/solid/devices/backends/udisks2/udisksdevicebackend.h --- a/src/solid/devices/backends/udisks2/udisksdevicebackend.h +++ b/src/solid/devices/backends/udisks2/udisksdevicebackend.h @@ -38,14 +38,18 @@ namespace UDisks2 { +class Manager; + class DeviceBackend: public QObject { Q_OBJECT public: static DeviceBackend *backendForUDI(const QString &udi, bool create = true); static void destroyBackend(const QString &udi); + static Manager *manager(); + static void setManager(Manager *manager); DeviceBackend(const QString &udi); ~DeviceBackend(); @@ -57,28 +61,24 @@ QStringList interfaces() const; const QString &udi() const; + void invalidateInterfaces(); void invalidateProperties(); + Q_SIGNALS: void propertyChanged(const QMap &changeMap); void changed(); -private Q_SLOTS: - void slotInterfacesAdded(const QDBusObjectPath &object_path, const VariantMapMap &interfaces_and_properties); - void slotInterfacesRemoved(const QDBusObjectPath &object_path, const QStringList &interfaces); - void slotPropertiesChanged(const QString &ifaceName, const QVariantMap &changedProps, const QStringList &invalidatedProps); - private: - void initInterfaces(); - QString introspect() const; void checkCache(const QString &key) const; QDBusInterface *m_device; mutable QVariantMap m_propertyCache; - QStringList m_interfaces; + mutable QStringList m_interfaces; QString m_udi; static QMap s_backends; + static Manager *s_manager; }; diff --git a/src/solid/devices/backends/udisks2/udisksdevicebackend.cpp b/src/solid/devices/backends/udisks2/udisksdevicebackend.cpp --- a/src/solid/devices/backends/udisks2/udisksdevicebackend.cpp +++ b/src/solid/devices/backends/udisks2/udisksdevicebackend.cpp @@ -25,16 +25,19 @@ #include #include -#include #include "solid/deviceinterface.h" #include "solid/genericinterface.h" +#include "udisksmanager.h" + using namespace Solid::Backends::UDisks2; /* Static cache for DeviceBackends for all UDIs */ QMap DeviceBackend::s_backends; +Manager *DeviceBackend::s_manager = nullptr; + DeviceBackend *DeviceBackend::backendForUDI(const QString &udi, bool create) { DeviceBackend *backend = nullptr; @@ -56,59 +59,33 @@ delete s_backends.take(udi); } -DeviceBackend::DeviceBackend(const QString &udi) - : m_udi(udi) +Manager *DeviceBackend::manager() { - //qDebug() << "Creating backend for device" << m_udi; - m_device = new QDBusInterface(UD2_DBUS_SERVICE, m_udi, - QString(), // no interface, we aggregate them - QDBusConnection::systemBus(), this); - - if (m_device->isValid()) { - QDBusConnection::systemBus().connect(UD2_DBUS_SERVICE, m_udi, DBUS_INTERFACE_PROPS, "PropertiesChanged", this, - SLOT(slotPropertiesChanged(QString,QVariantMap,QStringList))); - QDBusConnection::systemBus().connect(UD2_DBUS_SERVICE, UD2_DBUS_PATH, DBUS_INTERFACE_MANAGER, "InterfacesAdded", - this, SLOT(slotInterfacesAdded(QDBusObjectPath,VariantMapMap))); - QDBusConnection::systemBus().connect(UD2_DBUS_SERVICE, UD2_DBUS_PATH, DBUS_INTERFACE_MANAGER, "InterfacesRemoved", - this, SLOT(slotInterfacesRemoved(QDBusObjectPath,QStringList))); - - initInterfaces(); - } + return s_manager; } -DeviceBackend::~DeviceBackend() +void DeviceBackend::setManager(Manager *manager) { - //qDebug() << "Destroying backend for device" << m_udi; + s_manager = manager; } -void DeviceBackend::initInterfaces() +DeviceBackend::DeviceBackend(const QString &udi) + : m_udi(udi) { - m_interfaces.clear(); - - const QString xmlData = introspect(); - if (xmlData.isEmpty()) { - qCDebug(UDISKS2) << m_udi << "has no interfaces!"; - return; - } - - QDomDocument dom; - dom.setContent(xmlData); + Q_ASSERT(s_manager); + connect(this, &DeviceBackend::propertyChanged, this, &DeviceBackend::changed); +} - QDomNodeList ifaceNodeList = dom.elementsByTagName("interface"); - for (int i = 0; i < ifaceNodeList.count(); i++) { - QDomElement ifaceElem = ifaceNodeList.item(i).toElement(); - /* Accept only org.freedesktop.UDisks2.* interfaces so that when the device is unplugged, - * m_interfaces goes empty and we can easily verify that the device is gone. */ - if (!ifaceElem.isNull() && ifaceElem.attribute("name").startsWith(UD2_DBUS_SERVICE)) { - m_interfaces.append(ifaceElem.attribute("name")); - } - } +DeviceBackend::~DeviceBackend() +{ - //qDebug() << m_udi << "has interfaces:" << m_interfaces; } QStringList DeviceBackend::interfaces() const { + if (m_interfaces.isEmpty()) { + m_interfaces = s_manager->interfaces(m_udi); + } return m_interfaces; } @@ -126,50 +103,37 @@ bool DeviceBackend::propertyExists(const QString &key) const { checkCache(key); - /* checkCache() will put an invalid QVariant in cache when the property - * does not exist, so check for validity, not for an actual presence. */ return m_propertyCache.value(key).isValid(); } QVariantMap DeviceBackend::allProperties() const { - QDBusMessage call = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, m_udi, DBUS_INTERFACE_PROPS, "GetAll"); - - Q_FOREACH (const QString &iface, m_interfaces) { - call.setArguments(QVariantList() << iface); - QDBusPendingReply reply = QDBusConnection::systemBus().call(call); - - if (reply.isValid()) { - auto props = reply.value(); - // Can not use QMap<>::unite(), as it allows multiple values per key - for (auto it = props.cbegin(); it != props.cend(); ++it) { - m_propertyCache.insert(it.key(), it.value()); - } - } else { - qCWarning(UDISKS2) << "Error getting props:" << reply.error().name() << reply.error().message(); + m_propertyCache.clear(); + + const PropertyMap properties = s_manager->properties(m_udi); + + // flatten the properties into a single map as that is was the code was originally built upon + // TODO I bet there is a neat std::transform or something to do this? + for (auto it = properties.begin(), end = properties.end(); it != end; ++it) { + const auto props = it.value(); + + for (auto propsIt = props.begin(), propsEnd = props.end(); propsIt != propsEnd; ++propsIt) { + m_propertyCache.insert(propsIt.key(), propsIt.value()); } - //qDebug() << "After iface" << iface << ", cache now contains" << m_propertyCache.size() << "items"; } return m_propertyCache; } -void DeviceBackend::invalidateProperties() +void DeviceBackend::invalidateInterfaces() { - m_propertyCache.clear(); + m_interfaces.clear(); } -QString DeviceBackend::introspect() const +void DeviceBackend::invalidateProperties() { - QDBusMessage call = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, m_udi, - DBUS_INTERFACE_INTROSPECT, "Introspect"); - QDBusPendingReply reply = QDBusConnection::systemBus().call(call); - - if (reply.isValid()) { - return reply.value(); - } else { - return QString(); - } + invalidateInterfaces(); + m_propertyCache.clear(); } void DeviceBackend::checkCache(const QString &key) const @@ -182,76 +146,5 @@ return; } - QDBusMessage call = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, m_udi, DBUS_INTERFACE_PROPS, "Get"); - /* - * Interface is set to an empty string as in this QDBusInterface is a meta-object of multiple interfaces on the same path - * The DBus properties also interface supports this, and will find the appropriate interface if none is explicitly set. - * This matches what QDBusAbstractInterface would do - */ - call.setArguments(QVariantList() << QString() << key); - QDBusPendingReply reply = QDBusConnection::systemBus().call(call); - - /* We don't check for error here and store the item in the cache anyway so next time we don't have to - * do the DBus call to find out it does not exist but just check whether - * prop(key).isValid() */ - m_propertyCache.insert(key, reply.value()); -} - -void DeviceBackend::slotPropertiesChanged(const QString &ifaceName, const QVariantMap &changedProps, const QStringList &invalidatedProps) -{ - if (!ifaceName.startsWith(UD2_DBUS_SERVICE)) { - return; - } - //qDebug() << m_udi << "'s interface" << ifaceName << "changed props:"; - - QMap changeMap; - - Q_FOREACH (const QString &key, invalidatedProps) { - m_propertyCache.remove(key); - changeMap.insert(key, Solid::GenericInterface::PropertyModified); - //qDebug() << "\t invalidated:" << key; - } - - QMapIterator i(changedProps); - while (i.hasNext()) { - i.next(); - const QString key = i.key(); - m_propertyCache.insert(key, i.value()); // replace the value - changeMap.insert(key, Solid::GenericInterface::PropertyModified); - //qDebug() << "\t modified:" << key << ":" << m_propertyCache.value(key); - } - - emit propertyChanged(changeMap); - emit changed(); -} - -void DeviceBackend::slotInterfacesAdded(const QDBusObjectPath &object_path, const VariantMapMap &interfaces_and_properties) -{ - if (object_path.path() != m_udi) { - return; - } - - Q_FOREACH (const QString &iface, interfaces_and_properties.keys()) { - /* Don't store generic DBus interfaces */ - if (iface.startsWith(UD2_DBUS_SERVICE)) { - m_interfaces.append(iface); - } - } -} - -void DeviceBackend::slotInterfacesRemoved(const QDBusObjectPath &object_path, const QStringList &interfaces) -{ - if (object_path.path() != m_udi) { - return; - } - - Q_FOREACH (const QString &iface, interfaces) { - m_interfaces.removeAll(iface); - } - - // We don't know which property belongs to which interface, so remove all - m_propertyCache.clear(); - if (!m_interfaces.isEmpty()) { - allProperties(); - } + // old code fetched the property if neccessary but we have the manager take care of this } diff --git a/src/solid/devices/backends/udisks2/udisksmanager.h b/src/solid/devices/backends/udisks2/udisksmanager.h --- a/src/solid/devices/backends/udisks2/udisksmanager.h +++ b/src/solid/devices/backends/udisks2/udisksmanager.h @@ -38,6 +38,9 @@ namespace UDisks2 { +// interface -> [ {property: key}, {property: key}, ... ] +using PropertyMap = QMap; + class Manager: public Solid::Ifaces::DeviceManager { Q_OBJECT @@ -51,18 +54,27 @@ QString udiPrefix() const override; virtual ~Manager(); + QStringList interfaces(const QString &udi); + QMap allProperties(); + PropertyMap properties(const QString &udi); + private Q_SLOTS: void slotInterfacesAdded(const QDBusObjectPath &object_path, const VariantMapMap &interfaces_and_properties); void slotInterfacesRemoved(const QDBusObjectPath &object_path, const QStringList &interfaces); - void slotMediaChanged(const QDBusMessage &msg); + void slotPropertiesChanged(const QDBusMessage &msg); private: - const QStringList &deviceCache(); - void introspect(const QString &path, bool checkOptical = false); + QString opticalDriveFor(const QString &udi) const; + void checkOpticalMedium(const QString &udi, qulonglong oldSize, qulonglong newSize); + + // udi (dbus path) -> PropertyMap + QMap deviceCache(); + void updateBackend(const QString &udi); QSet m_supportedInterfaces; org::freedesktop::DBus::ObjectManager m_manager; - QStringList m_deviceCache; + QMap m_cache; + }; } diff --git a/src/solid/devices/backends/udisks2/udisksmanager.cpp b/src/solid/devices/backends/udisks2/udisksmanager.cpp --- a/src/solid/devices/backends/udisks2/udisksmanager.cpp +++ b/src/solid/devices/backends/udisks2/udisksmanager.cpp @@ -27,9 +27,9 @@ #include #include #include -#include #include "../shared/rootdevice.h" +#include "solid/genericinterface.h" using namespace Solid::Backends::UDisks2; using namespace Solid::Backends::Shared; @@ -74,15 +74,20 @@ this, SLOT(slotInterfacesAdded(QDBusObjectPath,VariantMapMap))); connect(&m_manager, SIGNAL(InterfacesRemoved(QDBusObjectPath,QStringList)), this, SLOT(slotInterfacesRemoved(QDBusObjectPath,QStringList))); + + QDBusConnection::systemBus().connect(UD2_DBUS_SERVICE, QString(), DBUS_INTERFACE_PROPS, "PropertiesChanged", this, + SLOT(slotPropertiesChanged(QDBusMessage))); } + + DeviceBackend::setManager(this); } Manager::~Manager() { - while (!m_deviceCache.isEmpty()) { - QString udi = m_deviceCache.takeFirst(); - DeviceBackend::destroyBackend(udi); + for (auto it = m_cache.constBegin(), end = m_cache.constEnd(); it != end; ++it) { + DeviceBackend::destroyBackend(it.key()); } + m_cache.clear(); } QObject *Manager::createDevice(const QString &udi) @@ -106,69 +111,50 @@ { QStringList result; + const auto devices = deviceCache(); if (!parentUdi.isEmpty()) { - Q_FOREACH (const QString &udi, deviceCache()) { - Device device(udi); + for (auto it = devices.keyBegin(), end = devices.keyEnd(); it != end; ++it) { + Device device(*it); if (device.queryDeviceInterface(type) && device.parentUdi() == parentUdi) { - result << udi; + result << *it; } } return result; } else if (type != Solid::DeviceInterface::Unknown) { - Q_FOREACH (const QString &udi, deviceCache()) { - Device device(udi); + for (auto it = devices.keyBegin(), end = devices.keyEnd(); it != end; ++it) { + Device device(*it); if (device.queryDeviceInterface(type)) { - result << udi; + result << *it; } } return result; } - return deviceCache(); + return devices.keys(); } QStringList Manager::allDevices() { - introspect("/org/freedesktop/UDisks2/block_devices", true /*checkOptical*/); - introspect("/org/freedesktop/UDisks2/drives"); + m_cache.clear(); - return m_deviceCache; -} + QDBusPendingReply reply = m_manager.GetManagedObjects(); + reply.waitForFinished(); + if (!reply.isError()) { + const auto items = reply.value(); + for (auto it = items.begin(), end = items.end(); it != end; ++it) { + const QString udi = it.key().path(); -void Manager::introspect(const QString &path, bool checkOptical) -{ - QDBusMessage call = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, path, - DBUS_INTERFACE_INTROSPECT, "Introspect"); - QDBusPendingReply reply = QDBusConnection::systemBus().call(call); - - if (reply.isValid()) { - QDomDocument dom; - dom.setContent(reply.value()); - QDomNodeList nodeList = dom.documentElement().elementsByTagName("node"); - for (int i = 0; i < nodeList.count(); i++) { - QDomElement nodeElem = nodeList.item(i).toElement(); - if (!nodeElem.isNull() && nodeElem.hasAttribute("name")) { - const QString udi = path + "/" + nodeElem.attribute("name"); - - if (checkOptical) { - Device device(udi); - if (device.mightBeOpticalDisc()) { - QDBusConnection::systemBus().connect(UD2_DBUS_SERVICE, udi, DBUS_INTERFACE_PROPS, "PropertiesChanged", this, - SLOT(slotMediaChanged(QDBusMessage))); - if (!device.isOpticalDisc()) { // skip empty CD disc - continue; - } - } - } - - m_deviceCache.append(udi); + if (!udi.startsWith(UD2_DBUS_PATH_BLOCK_DEVICES) && !udi.startsWith(UD2_DBUS_PATH_DRIVES)) { + continue; } + + m_cache.insert(udi, it.value()); } - } else { - qCWarning(UDISKS2) << "Failed enumerating UDisks2 objects:" << reply.error().name() << "\n" << reply.error().message(); } + + return m_cache.keys(); } QSet< Solid::DeviceInterface::Type > Manager::supportedInterfaces() const @@ -192,15 +178,33 @@ qCDebug(UDISKS2) << udi << "has new interfaces:" << interfaces_and_properties.keys(); - updateBackend(udi); + auto deviceIt = m_cache.find(udi); // new device, we don't know it yet - if (!m_deviceCache.contains(udi)) { - m_deviceCache.append(udi); + if (deviceIt == m_cache.end()) { + qCDebug(UDISKS2) << "\tIt's a new device, emitting added"; + // FIXME filter out generic DBus interfaces + m_cache.insert(udi, interfaces_and_properties); emit deviceAdded(udi); + return; + } + + qCDebug(UDISKS2) << "\tUpdating existing device"; + + // TODO "merge" / "unite"? + for (auto it = interfaces_and_properties.begin(), end = interfaces_and_properties.end(); it != end; ++it) { + /* Don't store generic DBus interfaces */ + if (!it.key().startsWith(UD2_DBUS_SERVICE)) { + continue; + } + deviceIt->insert(it.key(), it.value()); } + + updateBackend(udi); + // re-emit in case of 2-stage devices like N9 or some Android phones - else if (m_deviceCache.contains(udi) && interfaces_and_properties.keys().contains(UD2_DBUS_INTERFACE_FILESYSTEM)) { + // however, don't for optical media since this will be done in checkOpticalMedium when a medium arrives + if (interfaces_and_properties.contains(UD2_DBUS_INTERFACE_FILESYSTEM) && opticalDriveFor(udi).isEmpty()) { emit deviceAdded(udi); } } @@ -217,23 +221,26 @@ return; } + auto it = m_cache.find(udi); + if (it == m_cache.end()) { + return; + } + qCDebug(UDISKS2) << udi << "lost interfaces:" << interfaces; - /* - * Determine left interfaces. The device backend may have processed the - * InterfacesRemoved signal already, but the result set is the same - * independent if the backend or the manager processes the signal first. - */ - Device device(udi); - auto leftInterfaces = device.interfaces().toSet(); - leftInterfaces.subtract(interfaces.toSet()); - - if (leftInterfaces.isEmpty()) { - // remove the device if the last interface is removed + for (const QString &iface : interfaces) { + it->remove(iface); + } + + if (it->isEmpty()) { + qCDebug(UDISKS2) << "\tThere are no more interface, emitting device removal"; emit deviceRemoved(udi); - m_deviceCache.removeAll(udi); + m_cache.remove(udi); DeviceBackend::destroyBackend(udi); } else { + if (DeviceBackend *backend = DeviceBackend::backendForUDI(udi, false /*create*/)) { // can this even be null here? + backend->invalidateProperties(); + } /* * Changes in the interface composition may change if a device * matches a Predicate. We have to do a remove-and-readd cycle @@ -244,60 +251,147 @@ } } -void Manager::slotMediaChanged(const QDBusMessage &msg) +void Manager::slotPropertiesChanged(const QDBusMessage &msg) { - const QVariantMap properties = qdbus_cast(msg.arguments().at(1)); + const QString udi = msg.path(); - if (!properties.contains("Size")) { // react only on Size changes + if (udi.isEmpty() || !udi.startsWith(UD2_UDI_DISKS_PREFIX) || udi.startsWith(UD2_DBUS_PATH_JOBS)) { return; } - const QString udi = msg.path(); - updateBackend(udi); - qulonglong size = properties.value("Size").toULongLong(); - qCDebug(UDISKS2) << "MEDIA CHANGED in" << udi << "; size is:" << size; + auto cachedIt = m_cache.find(udi); + if (cachedIt == m_cache.end()) { + return; + } - if (!m_deviceCache.contains(udi) && size > 0) { // we don't know the optdisc, got inserted - m_deviceCache.append(udi); - emit deviceAdded(udi); + QMap changeMap; + + const QString iface = qdbus_cast(msg.arguments().at(0)); + const QVariantMap changed = qdbus_cast(msg.arguments().at(1)); + const QStringList invalidated = qdbus_cast(msg.arguments().at(2)); + + // Remember the current Size for the optical drive check below + const qulonglong oldSize = cachedIt->value(UD2_DBUS_INTERFACE_BLOCK).value(QStringLiteral("Size")).toULongLong(); + + for (const QString &prop : invalidated) { + (*cachedIt)[iface].remove(prop); + // TODO old code did this but isn't this a removal? + changeMap.insert(prop, Solid::GenericInterface::PropertyModified); + } + + for (auto it = changed.begin(), end = changed.end(); it != end; ++it) { + (*cachedIt)[iface].insert(it.key(), it.value()); + changeMap.insert(it.key(), Solid::GenericInterface::PropertyModified); + } + + if (!changeMap.isEmpty()) { + if (DeviceBackend *backend = DeviceBackend::backendForUDI(udi, false /*create*/)) { + // FIXME just update the backend cache instead of invalidating it + backend->invalidateProperties(); + emit backend->propertyChanged(changeMap); + } } - if (m_deviceCache.contains(udi) && size == 0) { // we know the optdisc, got removed + const qulonglong newSize = cachedIt->value(UD2_DBUS_INTERFACE_BLOCK).value(QStringLiteral("Size")).toULongLong(); + + checkOpticalMedium(udi, oldSize, newSize); +} + +QString Manager::opticalDriveFor(const QString &udi) const +{ + const PropertyMap deviceProps = m_cache.value(udi); + + // Check if the block belongs to an optical drive + const QString drivePath = deviceProps.value(UD2_DBUS_INTERFACE_BLOCK) + .value(QStringLiteral("Drive")) + .value().path(); + + if (drivePath.isEmpty() || drivePath == QLatin1String("/")) { + return QString(); + } + + auto driveIt = m_cache.constFind(drivePath); + if (driveIt == m_cache.constEnd()) { + qCDebug(UDISKS2) << "Block device" << udi << "belongs to a drive" << drivePath << "that doesn't exist"; + return QString(); + } + + const QStringList mediaCompatibility = driveIt->value(UD2_DBUS_INTERFACE_DRIVE) + .value(QStringLiteral("MediaCompatibility")).toStringList(); + + if (mediaCompatibility.filter(QStringLiteral("optical_")).isEmpty()) { + return QString(); + } + + return drivePath; +} + +void Manager::checkOpticalMedium(const QString &udi, qulonglong oldSize, qulonglong newSize) +{ + if (oldSize == newSize) { + return; + } + + if (opticalDriveFor(udi).isEmpty()) { + return; + } + + updateBackend(udi); + + if (newSize && !oldSize) { + emit deviceAdded(udi); + } else if (oldSize && !newSize) { emit deviceRemoved(udi); - m_deviceCache.removeAll(udi); DeviceBackend::destroyBackend(udi); } } -const QStringList &Manager::deviceCache() +QMap Manager::deviceCache() { - if (m_deviceCache.isEmpty()) { + if (m_cache.isEmpty()) { allDevices(); } - return m_deviceCache; + return m_cache; +} + +QStringList Manager::interfaces(const QString &udi) +{ + return deviceCache().value(udi).keys(); +} + +QMap Manager::allProperties() +{ + return deviceCache(); + +} + +PropertyMap Manager::properties(const QString &udi) +{ + return deviceCache().value(udi); } void Manager::updateBackend(const QString &udi) { - DeviceBackend *backend = DeviceBackend::backendForUDI(udi); + DeviceBackend *backend = DeviceBackend::backendForUDI(udi, false /*create*/); if (!backend) { return; } - //This doesn't emit "changed" signals. Signals are emitted later by DeviceBackend's slots - backend->allProperties(); + backend->invalidateProperties(); - QVariant driveProp = backend->prop("Drive"); - if (!driveProp.isValid()) { + // also update the drive + const QString drivePath = m_cache.value(udi).value(UD2_DBUS_INTERFACE_BLOCK).value(QStringLiteral("Drive")).value().path(); + if (drivePath.isEmpty() || drivePath == QLatin1String("/")) { return; } - QDBusObjectPath drivePath = qdbus_cast(driveProp); - DeviceBackend *driveBackend = DeviceBackend::backendForUDI(drivePath.path(), false); + DeviceBackend *driveBackend = DeviceBackend::backendForUDI(drivePath, false /*create*/); if (!driveBackend) { return; } + qCDebug(UDISKS2) << "\tUpdating drive" << drivePath; + driveBackend->invalidateProperties(); }