Solid

udisksmanager.cpp
1/*
2 SPDX-FileCopyrightText: 2012 Lukáš Tinkl <ltinkl@redhat.com>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "udisksmanager.h"
8#include "udisks_debug.h"
9#include "udisksdevicebackend.h"
10
11#include <QDBusConnection>
12#include <QDBusConnectionInterface>
13#include <QDBusMetaType>
14#include <QDBusObjectPath>
15#include <QDomDocument>
16
17#include "../shared/rootdevice.h"
18
19using namespace Solid::Backends::UDisks2;
20using namespace Solid::Backends::Shared;
21
22Manager::Manager(QObject *parent)
23 : Solid::Ifaces::DeviceManager(parent)
24 , m_manager(QStringLiteral(UD2_DBUS_SERVICE), QStringLiteral(UD2_DBUS_PATH), QDBusConnection::systemBus())
25{
26 m_supportedInterfaces = {
27 Solid::DeviceInterface::GenericInterface,
28 Solid::DeviceInterface::Block,
29 Solid::DeviceInterface::StorageAccess,
30 Solid::DeviceInterface::StorageDrive,
31 Solid::DeviceInterface::OpticalDrive,
32 Solid::DeviceInterface::OpticalDisc,
33 Solid::DeviceInterface::StorageVolume,
34 };
35
36 qDBusRegisterMetaType<QList<QDBusObjectPath>>();
37 qDBusRegisterMetaType<QVariantMap>();
38 qDBusRegisterMetaType<VariantMapMap>();
39 qDBusRegisterMetaType<DBUSManagerStruct>();
40
41 bool serviceFound = m_manager.isValid();
42 if (!serviceFound) {
43 // find out whether it will be activated automatically
44 QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"), //
45 QStringLiteral("/org/freedesktop/DBus"),
46 QStringLiteral("org.freedesktop.DBus"),
47 QStringLiteral("ListActivatableNames"));
48
50 if (reply.isValid() && reply.value().contains(QStringLiteral(UD2_DBUS_SERVICE))) {
51 QDBusConnection::systemBus().interface()->startService(QStringLiteral(UD2_DBUS_SERVICE));
52 serviceFound = true;
53 }
54 }
55
56 if (serviceFound) {
57 connect(&m_manager, SIGNAL(InterfacesAdded(QDBusObjectPath, VariantMapMap)), this, SLOT(slotInterfacesAdded(QDBusObjectPath, VariantMapMap)));
58 connect(&m_manager, SIGNAL(InterfacesRemoved(QDBusObjectPath, QStringList)), this, SLOT(slotInterfacesRemoved(QDBusObjectPath, QStringList)));
59 }
60}
61
62Manager::~Manager()
63{
64 while (!m_deviceCache.isEmpty()) {
65 QString udi = m_deviceCache.takeFirst();
66 DeviceBackend::destroyBackend(udi);
67 }
68}
69
70QObject *Manager::createDevice(const QString &udi)
71{
72 if (udi == udiPrefix()) {
73 RootDevice *root = new RootDevice(udi);
74
75 root->setProduct(tr("Storage"));
76 root->setDescription(tr("Storage devices"));
77 root->setIcon(QStringLiteral("server-database")); // Obviously wasn't meant for that, but maps nicely in oxygen icon set :-p
78
79 return root;
80 } else if (deviceCache().contains(udi)) {
81 return new Device(udi);
82 } else {
83 return nullptr;
84 }
85}
86
87QStringList Manager::devicesFromQuery(const QString &parentUdi, Solid::DeviceInterface::Type type)
88{
89 QStringList result;
90 const QStringList deviceList = deviceCache();
91
92 if (!parentUdi.isEmpty()) {
93 for (const QString &udi : deviceList) {
94 Device device(udi);
95 if (device.queryDeviceInterface(type) && device.parentUdi() == parentUdi) {
96 result << udi;
97 }
98 }
99
100 return result;
101 } else if (type != Solid::DeviceInterface::Unknown) {
102 for (const QString &udi : deviceList) {
103 Device device(udi);
104 if (device.queryDeviceInterface(type)) {
105 result << udi;
106 }
107 }
108
109 return result;
110 }
111
112 return deviceCache();
113}
114
115QStringList Manager::allDevices()
116{
117 m_deviceCache.clear();
118
119 introspect(QStringLiteral(UD2_DBUS_PATH_BLOCKDEVICES), true /*checkOptical*/);
120 introspect(QStringLiteral(UD2_DBUS_PATH_DRIVES));
121
122 return m_deviceCache;
123}
124
125void Manager::introspect(const QString &path, bool checkOptical)
126{
127 QDBusMessage call =
128 QDBusMessage::createMethodCall(QStringLiteral(UD2_DBUS_SERVICE), path, QStringLiteral(DBUS_INTERFACE_INTROSPECT), QStringLiteral("Introspect"));
130
131 if (reply.isValid()) {
132 QDomDocument dom;
133 dom.setContent(reply.value());
134 QDomNodeList nodeList = dom.documentElement().elementsByTagName(QStringLiteral("node"));
135 for (int i = 0; i < nodeList.count(); i++) {
136 QDomElement nodeElem = nodeList.item(i).toElement();
137 if (!nodeElem.isNull() && nodeElem.hasAttribute(QStringLiteral("name"))) {
138 const QString name = nodeElem.attribute(QStringLiteral("name"));
139 const QString udi = path + QStringLiteral("/") + name;
140
141 // Optimization, a loop device cannot really have a physical drive associated with it
142 if (checkOptical && !name.startsWith(QLatin1String("loop"))) {
143 Device device(udi);
144 if (device.mightBeOpticalDisc()) {
145 QDBusConnection::systemBus().connect(QStringLiteral(UD2_DBUS_SERVICE), //
146 udi,
147 QStringLiteral(DBUS_INTERFACE_PROPS),
148 QStringLiteral("PropertiesChanged"),
149 this,
150 SLOT(slotMediaChanged(QDBusMessage)));
151 if (!device.isOpticalDisc()) { // skip empty CD disc
152 continue;
153 }
154 }
155 }
156
157 m_deviceCache.append(udi);
158 }
159 }
160 } else {
161 qCWarning(UDISKS2) << "Failed enumerating UDisks2 objects:" << reply.error().name() << "\n" << reply.error().message();
162 }
163}
164
165QSet<Solid::DeviceInterface::Type> Manager::supportedInterfaces() const
166{
167 return m_supportedInterfaces;
168}
169
170QString Manager::udiPrefix() const
171{
172 return QStringLiteral(UD2_UDI_DISKS_PREFIX);
173}
174
175void Manager::slotInterfacesAdded(const QDBusObjectPath &object_path, const VariantMapMap &interfaces_and_properties)
176{
177 const QString udi = object_path.path();
178
179 /* Ignore jobs */
180 if (udi.startsWith(QStringLiteral(UD2_DBUS_PATH_JOBS))) {
181 return;
182 }
183
184 qCDebug(UDISKS2) << udi << "has new interfaces:" << interfaces_and_properties.keys();
185
186 // If device gained an org.freedesktop.UDisks2.Block interface, we
187 // should check if it is an optical drive, in order to properly
188 // register mediaChanged event handler with newly-plugged external
189 // drives
190 if (interfaces_and_properties.contains(QStringLiteral("org.freedesktop.UDisks2.Block"))) {
191 Device device(udi);
192 if (device.mightBeOpticalDisc()) {
193 QDBusConnection::systemBus().connect(QStringLiteral(UD2_DBUS_SERVICE), //
194 udi,
195 QStringLiteral(DBUS_INTERFACE_PROPS),
196 QStringLiteral("PropertiesChanged"),
197 this,
198 SLOT(slotMediaChanged(QDBusMessage)));
199 }
200 }
201
202 updateBackend(udi);
203
204 // new device, we don't know it yet
205 if (!m_deviceCache.contains(udi)) {
206 m_deviceCache.append(udi);
207 Q_EMIT deviceAdded(udi);
208 }
209 // re-emit in case of 2-stage devices like N9 or some Android phones
210 else if (m_deviceCache.contains(udi) && interfaces_and_properties.keys().contains(QStringLiteral(UD2_DBUS_INTERFACE_FILESYSTEM))) {
211 Q_EMIT deviceAdded(udi);
212 }
213}
214
215void Manager::slotInterfacesRemoved(const QDBusObjectPath &object_path, const QStringList &interfaces)
216{
217 const QString udi = object_path.path();
218 if (udi.isEmpty()) {
219 return;
220 }
221
222 /* Ignore jobs */
223 if (udi.startsWith(QStringLiteral(UD2_DBUS_PATH_JOBS))) {
224 return;
225 }
226
227 qCDebug(UDISKS2) << udi << "lost interfaces:" << interfaces;
228
229 /*
230 * Determine left interfaces. The device backend may have processed the
231 * InterfacesRemoved signal already, but the result set is the same
232 * independent if the backend or the manager processes the signal first.
233 */
234 Device device(udi);
235 const QStringList ifaceList = device.interfaces();
236 QSet<QString> leftInterfaces(ifaceList.begin(), ifaceList.end());
237 leftInterfaces.subtract(QSet<QString>(interfaces.begin(), interfaces.end()));
238
239 if (leftInterfaces.isEmpty()) {
240 // remove the device if the last interface is removed
241 Q_EMIT deviceRemoved(udi);
242 m_deviceCache.removeAll(udi);
243 DeviceBackend::destroyBackend(udi);
244 } else {
245 /*
246 * Changes in the interface composition may change if a device
247 * matches a Predicate. We have to do a remove-and-readd cycle
248 * as there is no dedicated signal for Predicate reevaluation.
249 */
250 Q_EMIT deviceRemoved(udi);
251 Q_EMIT deviceAdded(udi);
252 }
253}
254
255void Manager::slotMediaChanged(const QDBusMessage &msg)
256{
257 const QVariantMap properties = qdbus_cast<QVariantMap>(msg.arguments().at(1));
258
259 if (!properties.contains(QStringLiteral("Size"))) { // react only on Size changes
260 return;
261 }
262
263 const QString udi = msg.path();
264 updateBackend(udi);
265 qulonglong size = properties.value(QStringLiteral("Size")).toULongLong();
266 qCDebug(UDISKS2) << "MEDIA CHANGED in" << udi << "; size is:" << size;
267
268 Device device(udi);
269 if (!device.interfaces().contains(u"org.freedesktop.UDisks2.Filesystem")) {
270 if (!m_deviceCache.contains(udi) && size > 0) { // we don't know the optdisc, got inserted
271 m_deviceCache.append(udi);
272 Q_EMIT deviceAdded(udi);
273 }
274
275 if (m_deviceCache.contains(udi) && size == 0) { // we know the optdisc, got removed
276 Q_EMIT deviceRemoved(udi);
277 m_deviceCache.removeAll(udi);
278 DeviceBackend::destroyBackend(udi);
279 }
280 }
281}
282
283const QStringList &Manager::deviceCache()
284{
285 if (m_deviceCache.isEmpty()) {
286 allDevices();
287 }
288
289 return m_deviceCache;
290}
291
292void Manager::updateBackend(const QString &udi)
293{
294 DeviceBackend *backend = DeviceBackend::backendForUDI(udi);
295 if (!backend) {
296 return;
297 }
298
299 // This doesn't emit "changed" signals. Signals are emitted later by DeviceBackend's slots
300 backend->allProperties();
301
302 QVariant driveProp = backend->prop(QStringLiteral("Drive"));
303 if (!driveProp.isValid()) {
304 return;
305 }
306
307 QDBusObjectPath drivePath = qdbus_cast<QDBusObjectPath>(driveProp);
308 DeviceBackend *driveBackend = DeviceBackend::backendForUDI(drivePath.path(), false);
309 if (!driveBackend) {
310 return;
311 }
312
313 driveBackend->invalidateProperties();
314}
315
316#include "moc_udisksmanager.cpp"
Type
This enum type defines the type of device interface that a Device can have.
QString path(const QString &relativePath)
QString name(StandardAction id)
KGuiItem properties()
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)
QDBusConnectionInterface * interface() const const
QDBusConnection systemBus()
QDBusReply< void > startService(const QString &name)
QString message() const const
QString name() const const
QList< QVariant > arguments() const const
QDBusMessage createMethodCall(const QString &service, const QString &path, const QString &interface, const QString &method)
QString path() const const
QString path() const const
QDBusError error() const const
bool isValid() const const
typename Select< 0 >::Type value() const const
bool isValid() const const
QDomElement documentElement() const const
ParseResult setContent(QAnyStringView text, ParseOptions options)
QString attribute(const QString &name, const QString &defValue) const const
QDomNodeList elementsByTagName(const QString &tagname) const const
bool hasAttribute(const QString &name) const const
bool isNull() const const
QDomElement toElement() const const
int count() const const
QDomNode item(int index) const const
iterator begin()
void clear()
iterator end()
bool contains(const Key &key) const const
QList< Key > keys() const const
QString & append(QChar ch)
bool isEmpty() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool isValid() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:14:22 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.