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(UD2_DBUS_SERVICE, //
50 m_udi,
51 DBUS_INTERFACE_PROPS,
52 "PropertiesChanged",
53 this,
54 SLOT(slotPropertiesChanged(QString, QVariantMap, QStringList)));
55 QDBusConnection::systemBus().connect(UD2_DBUS_SERVICE,
56 UD2_DBUS_PATH,
57 DBUS_INTERFACE_MANAGER,
58 "InterfacesAdded",
59 this,
60 SLOT(slotInterfacesAdded(QDBusObjectPath, VariantMapMap)));
61 QDBusConnection::systemBus().connect(UD2_DBUS_SERVICE,
62 UD2_DBUS_PATH,
63 DBUS_INTERFACE_MANAGER,
64 "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(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(UD2_DBUS_SERVICE, //
129 m_udi,
130 DBUS_INTERFACE_PROPS,
131 "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 = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, m_udi, DBUS_INTERFACE_INTROSPECT, "Introspect");
161
162 if (reply.isValid()) {
163 return reply.value();
164 } else {
165 return QString();
166 }
167}
168
169void DeviceBackend::checkCache(const QString &key) const
170{
171 if (m_propertyCache.isEmpty()) { // recreate the cache
172 allProperties();
173 }
174
175 if (m_propertyCache.contains(key)) {
176 return;
177 }
178
179 QDBusMessage call = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, m_udi, DBUS_INTERFACE_PROPS, "Get");
180 /*
181 * Interface is set to an empty string as in this QDBusInterface is a meta-object of multiple interfaces on the same path
182 * The DBus properties also interface supports this, and will find the appropriate interface if none is explicitly set.
183 * This matches what QDBusAbstractInterface would do
184 */
185 call.setArguments(QVariantList() << QString() << key);
187
188 /* We don't check for error here and store the item in the cache anyway so next time we don't have to
189 * do the DBus call to find out it does not exist but just check whether
190 * prop(key).isValid() */
191 cacheProperty(key, reply.value());
192}
193
194void DeviceBackend::slotPropertiesChanged(const QString &ifaceName, const QVariantMap &changedProps, const QStringList &invalidatedProps)
195{
196 if (!ifaceName.startsWith(UD2_DBUS_SERVICE)) {
197 return;
198 }
199 // qDebug() << m_udi << "'s interface" << ifaceName << "changed props:";
200
201 QMap<QString, int> changeMap;
202
203 for (const QString &key : invalidatedProps) {
204 m_propertyCache.remove(key);
205 changeMap.insert(key, Solid::GenericInterface::PropertyModified);
206 // qDebug() << "\t invalidated:" << key;
207 }
208
209 QMapIterator<QString, QVariant> i(changedProps);
210 while (i.hasNext()) {
211 i.next();
212 const QString key = i.key();
213 cacheProperty(key, i.value()); // replace the value
214 changeMap.insert(key, Solid::GenericInterface::PropertyModified);
215 // qDebug() << "\t modified:" << key << ":" << m_propertyCache.value(key);
216 }
217
218 Q_EMIT propertyChanged(changeMap);
219 Q_EMIT changed();
220}
221
222void DeviceBackend::slotInterfacesAdded(const QDBusObjectPath &object_path, const VariantMapMap &interfaces_and_properties)
223{
224 if (object_path.path() != m_udi) {
225 return;
226 }
227
228 for (auto it = interfaces_and_properties.cbegin(); it != interfaces_and_properties.cend(); ++it) {
229 const QString &iface = it.key();
230 /* Don't store generic DBus interfaces */
231 if (iface.startsWith(UD2_DBUS_SERVICE)) {
232 m_interfaces.append(iface);
233 }
234 }
235}
236
237void DeviceBackend::slotInterfacesRemoved(const QDBusObjectPath &object_path, const QStringList &interfaces)
238{
239 if (object_path.path() != m_udi) {
240 return;
241 }
242
243 for (const QString &iface : interfaces) {
244 m_interfaces.removeAll(iface);
245 }
246
247 // We don't know which property belongs to which interface, so remove all
248 m_propertyCache.clear();
249 if (!m_interfaces.isEmpty()) {
250 allProperties();
251 }
252}
253
254// UDisks2 sends us null terminated strings, make sure to strip the extranous \0 in favor of the implicit \0.
255// Otherwise comparision becomes unnecessarily complicated because 'foo\0' != 'foo'. QByteArrays are implicitly
256// terminated already.
257void DeviceBackend::cacheProperty(const QString &key, const QVariant &value) const
258{
259 if (value.metaType() == QMetaType::fromType<QByteArray>()) {
260 auto blob = value.toByteArray();
261 while (blob.endsWith('\0')) {
262 blob.chop(1);
263 }
264 m_propertyCache.insert(key, blob);
265 } else {
266 m_propertyCache.insert(key, value);
267 }
268}
269
270#include "moc_udisksdevicebackend.cpp"
QString name(StandardShortcut id)
void chop(qsizetype n)
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)
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 Tue Mar 26 2024 11:17:12 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.