Plasma5Support

ksolidnotify.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Jacopo De Simoi <wilderkde@gmail.com>
3 SPDX-FileCopyrightText: 2014 Lukáš Tinkl <ltinkl@redhat.com>
4 SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
9#include "ksolidnotify.h"
10
11#include <Solid/DeviceInterface>
12#include <Solid/DeviceNotifier>
13#include <Solid/OpticalDisc>
14#include <Solid/OpticalDrive>
15#include <Solid/PortableMediaPlayer>
16#include <Solid/Predicate>
17#include <Solid/StorageAccess>
18#include <Solid/StorageDrive>
19#include <Solid/StorageVolume>
20
21#include <KLocalizedString>
22#include <KNotification>
23#include <processcore/process.h>
24#include <processcore/processes.h>
25
26#include <QList>
27#include <QProcess>
28#include <QRegularExpression>
29#include <QStringList>
30#include <QStringView>
31
32KSolidNotify::KSolidNotify(QObject *parent)
33 : QObject(parent)
34{
35 Solid::Predicate p(Solid::DeviceInterface::StorageAccess);
36 p |= Solid::Predicate(Solid::DeviceInterface::OpticalDrive);
37 p |= Solid::Predicate(Solid::DeviceInterface::PortableMediaPlayer);
39 for (const Solid::Device &dev : devices) {
40 m_devices.insert(dev.udi(), dev);
41 connectSignals(&m_devices[dev.udi()]);
42 }
43
44 connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded, this, &KSolidNotify::onDeviceAdded);
45 connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved, this, &KSolidNotify::onDeviceRemoved);
46}
47
48void KSolidNotify::onDeviceAdded(const QString &udi)
49{
50 // Clear any stale message from a previous instance
51 Q_EMIT clearNotification(udi);
52 Solid::Device device(udi);
53 m_devices.insert(udi, device);
54 connectSignals(&m_devices[udi]);
55}
56
57void KSolidNotify::onDeviceRemoved(const QString &udi)
58{
59 if (m_devices[udi].is<Solid::StorageVolume>()) {
60 Solid::StorageAccess *access = m_devices[udi].as<Solid::StorageAccess>();
61 if (access) {
62 disconnect(access, nullptr, this, nullptr);
63 }
64 }
65 m_devices.remove(udi);
66}
67
68bool KSolidNotify::isSafelyRemovable(const QString &udi) const
69{
70 Solid::Device parent = m_devices[udi].parent();
71 if (parent.is<Solid::StorageDrive>()) {
73 return (!drive->isInUse() && (drive->isHotpluggable() || drive->isRemovable()));
74 }
75
76 const Solid::StorageAccess *access = m_devices[udi].as<Solid::StorageAccess>();
77 if (access) {
78 return !m_devices[udi].as<Solid::StorageAccess>()->isAccessible();
79 } else {
80 // If this check fails, the device has been already physically
81 // ejected, so no need to say that it is safe to remove it
82 return false;
83 }
84}
85
86void KSolidNotify::connectSignals(Solid::Device *device)
87{
88 Solid::StorageAccess *access = device->as<Solid::StorageAccess>();
89 if (access) {
90 connect(access, &Solid::StorageAccess::teardownDone, this, [this](Solid::ErrorType error, const QVariant &errorData, const QString &udi) {
91 onSolidReply(SolidReplyType::Teardown, error, errorData, udi);
92 });
93
94 connect(access, &Solid::StorageAccess::setupDone, this, [this](Solid::ErrorType error, const QVariant &errorData, const QString &udi) {
95 onSolidReply(SolidReplyType::Setup, error, errorData, udi);
96 });
97 }
98 if (device->is<Solid::OpticalDisc>()) {
100 connect(drive, &Solid::OpticalDrive::ejectDone, this, [this](Solid::ErrorType error, const QVariant &errorData, const QString &udi) {
101 onSolidReply(SolidReplyType::Eject, error, errorData, udi);
102 });
103 }
104}
105
106void KSolidNotify::queryBlockingApps(const QString &devicePath)
107{
108 QProcess *p = new QProcess;
110 Q_EMIT blockingAppsReady({});
111 p->deleteLater();
112 });
113 connect(p, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), [=, this](int, QProcess::ExitStatus) {
114 QStringList blockApps;
116 const auto pidList = QStringView(out).split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts);
117 KSysGuard::Processes procs;
118 for (const QStringView &pidStr : pidList) {
119 int pid = pidStr.toInt();
120 if (!pid) {
121 continue;
122 }
123 procs.updateOrAddProcess(pid);
124 KSysGuard::Process *proc = procs.getProcess(pid);
125 if (!blockApps.contains(proc->name())) {
126 blockApps << proc->name();
127 }
128 }
129 blockApps.removeDuplicates();
130 Q_EMIT blockingAppsReady(blockApps);
131 p->deleteLater();
132 });
133 p->start(QStringLiteral("lsof"), {QStringLiteral("-t"), devicePath});
134 // p.start(QStringLiteral("fuser"), {QStringLiteral("-m"), devicePath});
135}
136
137void KSolidNotify::onSolidReply(SolidReplyType type, Solid::ErrorType error, const QVariant &errorData, const QString &udi)
138{
139 if ((error == Solid::ErrorType::NoError) && (type == SolidReplyType::Setup)) {
140 Q_EMIT clearNotification(udi);
141 return;
142 }
143
144 QString errorMsg;
145
146 switch (error) {
147 case Solid::ErrorType::NoError:
148 if (type != SolidReplyType::Setup && isSafelyRemovable(udi)) {
149 KNotification::event(QStringLiteral("safelyRemovable"), i18n("Device Status"), i18n("A device can now be safely removed"));
150 errorMsg = i18n("This device can now be safely removed.");
151 }
152 break;
153
154 case Solid::ErrorType::UnauthorizedOperation:
155 switch (type) {
156 case SolidReplyType::Setup:
157 errorMsg = i18n("You are not authorized to mount this device.");
158 break;
159 case SolidReplyType::Teardown:
160 errorMsg = i18nc("Remove is less technical for unmount", "You are not authorized to remove this device.");
161 break;
162 case SolidReplyType::Eject:
163 errorMsg = i18n("You are not authorized to eject this disc.");
164 break;
165 }
166
167 break;
168 case Solid::ErrorType::DeviceBusy: {
169 if (type == SolidReplyType::Setup) { // can this even happen?
170 errorMsg = i18n("Could not mount this device as it is busy.");
171 } else {
172 Solid::Device device;
173
174 if (type == SolidReplyType::Eject) {
175 QString discUdi;
176 for (const Solid::Device &device : std::as_const(m_devices)) {
177 if (device.parentUdi() == udi) {
178 discUdi = device.udi();
179 }
180 }
181
182 if (discUdi.isNull()) {
183 // This should not happen, bail out
184 return;
185 }
186
187 device = Solid::Device(discUdi);
188 } else {
189 device = Solid::Device(udi);
190 }
191
192 Solid::StorageAccess *access = device.as<Solid::StorageAccess>();
193
194 // Without that, our lambda function would capture an uninitialized object, resulting in UB
195 // and random crashes
197 *c = connect(this, &KSolidNotify::blockingAppsReady, [=, this](const QStringList &blockApps) {
199 if (blockApps.isEmpty()) {
200 errorMessage = i18n("One or more files on this device are open within an application.");
201 } else {
202 errorMessage = i18np("One or more files on this device are opened in application \"%2\".",
203 "One or more files on this device are opened in following applications: %2.",
204 blockApps.count(),
205 blockApps.join(i18nc("separator in list of apps blocking device unmount", ", ")));
206 }
207 Q_EMIT notify(error, errorMessage, errorData.toString(), udi);
208 disconnect(*c);
209 delete c;
210 });
211 queryBlockingApps(access->filePath());
212 }
213
214 break;
215 }
216 case Solid::ErrorType::UserCanceled:
217 // don't point out the obvious to the user, do nothing here
218 break;
219 default:
220 switch (type) {
221 case SolidReplyType::Setup:
222 errorMsg = i18n("Could not mount this device.");
223 break;
224 case SolidReplyType::Teardown:
225 errorMsg = i18nc("Remove is less technical for unmount", "Could not remove this device.");
226 break;
227 case SolidReplyType::Eject:
228 errorMsg = i18n("Could not eject this disc.");
229 break;
230 }
231
232 break;
233 }
234
235 if (!errorMsg.isEmpty()) {
236 Q_EMIT notify(error, errorMsg, errorData.toString(), udi);
237 }
238}
static KNotification * event(const QString &eventId, const QString &text=QString(), const QPixmap &pixmap=QPixmap(), const NotificationFlags &flags=CloseOnTimeout, const QString &componentName=QString())
void deviceRemoved(const QString &udi)
void deviceAdded(const QString &udi)
QString udi() const
QString parentUdi() const
Device parent() const
bool is() const
static QList< Device > listFromQuery(const Predicate &predicate, const QString &parentUdi=QString())
DevIface * as()
void ejectDone(Solid::ErrorType error, QVariant errorData, const QString &udi)
QString filePath() const
void teardownDone(Solid::ErrorType error, QVariant errorData, const QString &udi)
void setupDone(Solid::ErrorType error, QVariant errorData, const QString &udi)
bool isInUse() const
bool isHotpluggable() const
bool isRemovable() const
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
iterator insert(const Key &key, const T &value)
bool remove(const Key &key)
QByteArray readAll()
qsizetype count() const const
bool isEmpty() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
void errorOccurred(QProcess::ProcessError error)
void finished(int exitCode, QProcess::ExitStatus exitStatus)
void start(OpenMode mode)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
bool isNull() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
qsizetype removeDuplicates()
QList< QStringView > split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
SkipEmptyParts
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:08:57 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.