Solid

udisksdevicebackend.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Michael Zanetti <mzanetti@kde.org>
3 SPDX-FileCopyrightText: 2010-2012 Lukáš Tinkl <ltinkl@redhat.com>
4 SPDX-FileCopyrightText: 2012 Dan Vrátil <dvratil@redhat.com>
5
6 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7*/
8
9#include "udisksdevicebackend.h"
10#include "udisks_debug.h"
11
12#include <QDBusConnection>
13#include <QXmlStreamReader>
14
15#include "solid/deviceinterface.h"
16#include "solid/genericinterface.h"
17
18using namespace Solid::Backends::UDisks2;
19
20/* Static cache for DeviceBackends for all UDIs */
21QThreadStorage<QMap<QString /* UDI */, DeviceBackend *>> DeviceBackend::s_backends;
22
23DeviceBackend *DeviceBackend::backendForUDI(const QString &udi, bool create)
24{
25 DeviceBackend *backend = nullptr;
26 if (udi.isEmpty()) {
27 return backend;
28 }
29
30 backend = s_backends.localData().value(udi);
31 if (!backend && create) {
32 backend = new DeviceBackend(udi);
33 s_backends.localData().insert(udi, backend);
34 }
35
36 return backend;
37}
38
39void DeviceBackend::destroyBackend(const QString &udi)
40{
41 delete s_backends.localData().take(udi);
42}
43
44DeviceBackend::DeviceBackend(const QString &udi)
45 : m_udi(udi)
46{
47 // qDebug() << "Creating backend for device" << m_udi;
48
49 QDBusConnection::systemBus().connect(QStringLiteral(UD2_DBUS_SERVICE), //
50 m_udi,
51 QStringLiteral(DBUS_INTERFACE_PROPS),
52 QStringLiteral("PropertiesChanged"),
53 this,
54 SLOT(slotPropertiesChanged(QString, QVariantMap, QStringList)));
55 QDBusConnection::systemBus().connect(QStringLiteral(UD2_DBUS_SERVICE),
56 QStringLiteral(UD2_DBUS_PATH),
57 QStringLiteral(DBUS_INTERFACE_MANAGER),
58 QStringLiteral("InterfacesAdded"),
59 this,
60 SLOT(slotInterfacesAdded(QDBusObjectPath, VariantMapMap)));
61 QDBusConnection::systemBus().connect(QStringLiteral(UD2_DBUS_SERVICE),
62 QStringLiteral(UD2_DBUS_PATH),
63 QStringLiteral(DBUS_INTERFACE_MANAGER),
64 QStringLiteral("InterfacesRemoved"),
65 this,
66 SLOT(slotInterfacesRemoved(QDBusObjectPath, QStringList)));
67
68 initInterfaces();
69}
70
71DeviceBackend::~DeviceBackend()
72{
73 // qDebug() << "Destroying backend for device" << m_udi;
74}
75
76void DeviceBackend::initInterfaces()
77{
78 m_interfaces.clear();
79
80 const QString xmlData = introspect();
81 if (xmlData.isEmpty()) {
82 qCDebug(UDISKS2) << m_udi << "has no interfaces!";
83 return;
84 }
85
86 QXmlStreamReader xml(xmlData);
87 while (!xml.atEnd() && !xml.hasError()) {
88 xml.readNext();
89 if (xml.isStartElement() && xml.name() == QLatin1String("interface")) {
90 const auto name = xml.attributes().value(QLatin1String("name")).toString();
91 /* Accept only org.freedesktop.UDisks2.* interfaces so that when the device is unplugged,
92 * m_interfaces goes empty and we can easily verify that the device is gone. */
93 if (name.startsWith(QStringLiteral(UD2_DBUS_SERVICE))) {
94 m_interfaces.append(name);
95 }
96 }
97 }
98
99 // qDebug() << m_udi << "has interfaces:" << m_interfaces;
100}
101
102QStringList DeviceBackend::interfaces() const
103{
104 return m_interfaces;
105}
106
107const QString &DeviceBackend::udi() const
108{
109 return m_udi;
110}
111
112QVariant DeviceBackend::prop(const QString &key) const
113{
114 checkCache(key);
115 return m_propertyCache.value(key);
116}
117
118bool DeviceBackend::propertyExists(const QString &key) const
119{
120 checkCache(key);
121 /* checkCache() will put an invalid QVariant in cache when the property
122 * does not exist, so check for validity, not for an actual presence. */
123 return m_propertyCache.value(key).isValid();
124}
125
126QVariantMap DeviceBackend::allProperties() const
127{
128 QDBusMessage call = QDBusMessage::createMethodCall(QStringLiteral(UD2_DBUS_SERVICE), //
129 m_udi,
130 QStringLiteral(DBUS_INTERFACE_PROPS),
131 QStringLiteral("GetAll"));
132
133 for (const QString &iface : std::as_const(m_interfaces)) {
134 call.setArguments(QVariantList() << iface);
136
137 if (reply.isValid()) {
138 auto props = reply.value();
139 // Can not use QMap<>::unite(), as it allows multiple values per key
140 for (auto it = props.cbegin(); it != props.cend(); ++it) {
141 cacheProperty(it.key(), it.value());
142 }
143 } else {
144 qCWarning(UDISKS2) << "Error getting props:" << reply.error().name() << reply.error().message() << "for" << m_udi;
145 }
146 // qDebug() << "After iface" << iface << ", cache now contains" << m_propertyCache.size() << "items";
147 }
148
149 return m_propertyCache;
150}
151
152void DeviceBackend::invalidateProperties()
153{
154 m_propertyCache.clear();
155}
156
157QString DeviceBackend::introspect() const
158{
159 QDBusMessage call =
160 QDBusMessage::createMethodCall(QStringLiteral(UD2_DBUS_SERVICE), m_udi, QStringLiteral(DBUS_INTERFACE_INTROSPECT), QStringLiteral("Introspect"));
162
163 if (reply.isValid()) {
164 return reply.value();
165 } else {
166 return QString();
167 }
168}
169
170void DeviceBackend::checkCache(const QString &key) const
171{
172 if (m_propertyCache.isEmpty()) { // recreate the cache
173 allProperties();
174 }
175
176 if (m_propertyCache.contains(key)) {
177 return;
178 }
179
180 QDBusMessage call = QDBusMessage::createMethodCall(QStringLiteral(UD2_DBUS_SERVICE), m_udi, QStringLiteral(DBUS_INTERFACE_PROPS), QStringLiteral("Get"));
181 /*
182 * Interface is set to an empty string as in this QDBusInterface is a meta-object of multiple interfaces on the same path
183 * The DBus properties also interface supports this, and will find the appropriate interface if none is explicitly set.
184 * This matches what QDBusAbstractInterface would do
185 */
186 call.setArguments(QVariantList() << QString() << key);
188
189 /* We don't check for error here and store the item in the cache anyway so next time we don't have to
190 * do the DBus call to find out it does not exist but just check whether
191 * prop(key).isValid() */
192 cacheProperty(key, reply.value());
193}
194
195void DeviceBackend::slotPropertiesChanged(const QString &ifaceName, const QVariantMap &changedProps, const QStringList &invalidatedProps)
196{
197 if (!ifaceName.startsWith(QStringLiteral(UD2_DBUS_SERVICE))) {
198 return;
199 }
200 // qDebug() << m_udi << "'s interface" << ifaceName << "changed props:";
201
202 QMap<QString, int> changeMap;
203
204 for (const QString &key : invalidatedProps) {
205 m_propertyCache.remove(key);
206 changeMap.insert(key, Solid::GenericInterface::PropertyModified);
207 // qDebug() << "\t invalidated:" << key;
208 }
209
210 QMapIterator<QString, QVariant> i(changedProps);
211 while (i.hasNext()) {
212 i.next();
213 const QString key = i.key();
214 cacheProperty(key, i.value()); // replace the value
215 changeMap.insert(key, Solid::GenericInterface::PropertyModified);
216 // qDebug() << "\t modified:" << key << ":" << m_propertyCache.value(key);
217 }
218
219 Q_EMIT propertyChanged(changeMap);
220 Q_EMIT changed();
221}
222
223void DeviceBackend::slotInterfacesAdded(const QDBusObjectPath &object_path, const VariantMapMap &interfaces_and_properties)
224{
225 if (object_path.path() != m_udi) {
226 return;
227 }
228
229 for (auto it = interfaces_and_properties.cbegin(); it != interfaces_and_properties.cend(); ++it) {
230 const QString &iface = it.key();
231 /* Don't store generic DBus interfaces */
232 if (iface.startsWith(QStringLiteral(UD2_DBUS_SERVICE))) {
233 m_interfaces.append(iface);
234 }
235 }
236}
237
238void DeviceBackend::slotInterfacesRemoved(const QDBusObjectPath &object_path, const QStringList &interfaces)
239{
240 if (object_path.path() != m_udi) {
241 return;
242 }
243
244 for (const QString &iface : interfaces) {
245 m_interfaces.removeAll(iface);
246 }
247
248 // We don't know which property belongs to which interface, so remove all
249 m_propertyCache.clear();
250 if (!m_interfaces.isEmpty()) {
251 allProperties();
252 }
253}
254
255// UDisks2 sends us null terminated strings, make sure to strip the extranous \0 in favor of the implicit \0.
256// Otherwise comparision becomes unnecessarily complicated because 'foo\0' != 'foo'. QByteArrays are implicitly
257// terminated already.
258void DeviceBackend::cacheProperty(const QString &key, const QVariant &value) const
259{
260 if (value.metaType() == QMetaType::fromType<QByteArray>()) {
261 auto blob = value.toByteArray();
262 while (blob.endsWith('\0')) {
263 blob.chop(1);
264 }
265 m_propertyCache.insert(key, blob);
266 } else {
267 m_propertyCache.insert(key, value);
268 }
269}
270
271#include "moc_udisksdevicebackend.cpp"
QString name(StandardAction id)
QDBusMessage call(const QDBusMessage &message, QDBus::CallMode mode, int timeout) const const
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
QDBusConnection systemBus()
QString message() const const
QString name() const const
QDBusMessage createMethodCall(const QString &service, const QString &path, const QString &interface, const QString &method)
void setArguments(const QList< QVariant > &arguments)
QString path() const const
QDBusError error() const const
bool isValid() const const
typename Select< 0 >::Type value() const const
void append(QList< T > &&value)
void clear()
bool isEmpty() const const
qsizetype removeAll(const AT &t)
const_iterator cbegin() const const
const_iterator cend() const const
iterator insert(const Key &key, const T &value)
QMetaType fromType()
Q_EMITQ_EMIT
bool isEmpty() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QMetaType metaType() const const
QByteArray toByteArray() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:03:23 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.