soliduiserver: implement mountpoint cleaner

lots of references have to be kept and passed around because once the
device is removed Solid::Device is just an UDI - anything else is
obtained dynamically and will return invalid results (even casts will
not work)

what the mountpoint cleaner does? when a device is removed without
being unmounted (e.g. USB storage) the program will call `unmount`
essentially making sure the device node and the mount point can be used
when the device is inserted again, no other project does that as far as
I am aware

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
This commit is contained in:
Ivailo Monev 2023-09-09 10:56:55 +03:00
parent dd4b27b579
commit 470b52d29c
10 changed files with 166 additions and 50 deletions

View file

@ -222,7 +222,12 @@ void SolidRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryM
foreach (const KServiceAction &kserviceaction, kserviceactions) {
if (kserviceaction.name() == actionname) {
const Solid::Device soliddevice(kSolidUDI(match.id()));
QStringList actioncommand = kSolidActionCommand(kserviceaction.exec(), soliddevice, true);
const Solid::Block *solidblock = soliddevice.as<Solid::Block>();
QStringList actioncommand = kSolidActionCommand(
kserviceaction.exec(), soliddevice,
solidblock ? solidblock->device() : QString(),
true
);
if (actioncommand.size() == 0) {
kWarning() << "invalid action command" << actionname << "in" << actionfilepath;
return;

View file

@ -17,12 +17,21 @@ set(kded_soliduiserver_SRCS
)
kde4_add_plugin(kded_soliduiserver ${kded_soliduiserver_SRCS})
target_link_libraries(kded_soliduiserver
KDE4::solid
KDE4::kio
)
set(solid_unmount_device_SRCS
solid_unmount_device.cpp
)
add_executable(solid_unmount_device ${solid_unmount_device_SRCS})
target_link_libraries(solid_unmount_device
KDE4::kdecore
KDE4::kdeui
)
########### install files ###############
install(
@ -41,7 +50,7 @@ install(
)
install(
TARGETS soliduiserver_helper
TARGETS soliduiserver_helper solid_unmount_device
DESTINATION ${KDE4_LIBEXEC_INSTALL_DIR}
)

View file

@ -1,4 +1,6 @@
install(
FILES openinwindow.desktop
FILES
openinwindow.desktop
unmountdevice.desktop
DESTINATION ${KDE4_DATA_INSTALL_DIR}/solid/actions
)

View file

@ -0,0 +1,10 @@
[Desktop Entry]
X-KDE-Solid-Predicate=IS Block
X-KDE-Solid-When=Remove
Type=Service
Actions=unmount;
[Desktop Action unmount]
Name=Unmount the device
Exec=solid_unmount_device %d
Icon=media-eject

View file

@ -0,0 +1,58 @@
/* This file is part of the KDE project
Copyright (C) 2023 Ivailo Monev <xakepa10@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2, as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <QDBusInterface>
#include <QCoreApplication>
#include <KAboutData>
#include <KCmdLineArgs>
#include <KLocale>
#include <KMountPoint>
#include <KDebug>
int main(int argc, char **argv) {
KAboutData aboutData(
"solid_unmount_device", 0, ki18n("solid_unmount_device"),
"1.0.0", ki18n("Device unmounter for KDE."),
KAboutData::License_GPL_V2,
ki18n("(c) 2023 Ivailo Monev")
);
aboutData.addAuthor(ki18n("Ivailo Monev"), ki18n("Maintainer"), "xakepa10@gmail.com");
aboutData.setProgramIconName(QLatin1String("media-eject"));
KCmdLineArgs::init(argc, argv, &aboutData);
KCmdLineOptions option;
option.add("+[device]", ki18n("device to be unmounted"));
KCmdLineArgs::addCmdLineOptions(option);
QCoreApplication kapplication(argc, argv);
QDBusInterface soliduiserver("org.kde.kded", "/modules/soliduiserver", "org.kde.SolidUiServer");
const KMountPoint::List mountpoints = KMountPoint::currentMountPoints();
KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
for (int pos = 0; pos < args->count(); pos++) {
const QString argstring = args->arg(pos);
const KMountPoint::Ptr mp = mountpoints.findByDevice(argstring);
if (mp && !mp->mountPoint().isEmpty()) {
kDebug() << "Unmounting" << argstring;
soliduiserver.call("unmountDevice", argstring);
}
}
kDebug() << "All done";
return 0;
}

View file

@ -24,13 +24,11 @@
#include <kglobal.h>
#include <kconfiggroup.h>
SolidUiDialog::SolidUiDialog(const Solid::Device &soliddevice,
const QList<KServiceAction> &kserviceactions,
SolidUiDialog::SolidUiDialog(const SolidUiAction &solidaction,
const bool mount,
QWidget *parent)
: KDialog(parent),
m_soliddevice(soliddevice),
m_serviceactions(kserviceactions),
m_solidaction(solidaction),
m_mount(mount),
m_mainwidget(nullptr),
m_mainlayout(nullptr),
@ -38,10 +36,10 @@ SolidUiDialog::SolidUiDialog(const Solid::Device &soliddevice,
m_devicelabel(nullptr),
m_listwidget(nullptr)
{
Q_ASSERT(kserviceactions.size() > 0);
const KIcon deviceicon = KIcon(m_soliddevice.icon());
Q_ASSERT(solidaction.actions.size() > 0);
const KIcon deviceicon = KIcon(solidaction.device.icon());
setWindowIcon(deviceicon);
setWindowTitle(i18n("Actions for %1", m_soliddevice.description()));
setWindowTitle(i18n("Actions for %1", solidaction.device.description()));
setButtons(KDialog::Ok | KDialog::Cancel);
setDefaultButton(KDialog::Ok);
@ -62,7 +60,7 @@ SolidUiDialog::SolidUiDialog(const Solid::Device &soliddevice,
m_listwidget = new KListWidget(m_mainwidget);
int itemscounter = 0;
foreach (const KServiceAction &kserviceaction, kserviceactions) {
foreach (const KServiceAction &kserviceaction, solidaction.actions) {
QListWidgetItem* listitem = new QListWidgetItem(m_listwidget);
listitem->setText(kserviceaction.text());
listitem->setIcon(KIcon(kserviceaction.icon()));
@ -112,8 +110,8 @@ void SolidUiDialog::slotOkClicked()
}
const QListWidgetItem* selecteditem = selecteditems.first();
const int kserviceactionindex = selecteditem->data(Qt::UserRole).toInt();
Q_ASSERT(kserviceactionindex >= 0 && kserviceactionindex < m_serviceactions.size());
kExecuteAction(m_serviceactions.at(kserviceactionindex), m_soliddevice, m_mount);
Q_ASSERT(kserviceactionindex >= 0 && kserviceactionindex < solidaction.actions.size());
kExecuteAction(m_solidaction.actions.at(kserviceactionindex), m_solidaction.device, m_solidaction.devicenode, m_mount);
}
#include "moc_soliduidialog.cpp"

View file

@ -19,6 +19,8 @@
#ifndef SOLIDUIDIALOG_H
#define SOLIDUIDIALOG_H
#include "soliduiserver_common.h"
#include <QGridLayout>
#include <QLabel>
#include <kdialog.h>
@ -31,9 +33,7 @@ class SolidUiDialog : public KDialog
{
Q_OBJECT
public:
SolidUiDialog(const Solid::Device &soliddevce,
const QList<KServiceAction> &kserviceactions,
const bool mount,
SolidUiDialog(const SolidUiAction &soliddevce, const bool mount,
QWidget *parent = nullptr);
~SolidUiDialog();
@ -42,8 +42,7 @@ private Q_SLOTS:
void slotOkClicked();
private:
Solid::Device m_soliddevice;
QList<KServiceAction> m_serviceactions;
SolidUiAction m_solidaction;
bool m_mount;
QWidget* m_mainwidget;
QGridLayout* m_mainlayout;

View file

@ -52,13 +52,41 @@ static void kNotifyError(const Solid::ErrorType error, const bool unmount)
KNotification::event("soliduiserver/mounterror", title, Solid::errorString(error));
}
static SolidUiActions kSolidDeviceActions()
{
SolidUiActions result;
const QList<Solid::Device> soliddevices = Solid::Device::allDevices();
foreach (const Solid::Device &soliddevice, soliddevices) {
const QStringList solidactions = KGlobal::dirs()->findAllResources("data", "solid/actions/");
foreach (const QString &solidaction, solidactions) {
KDesktopFile kdestopfile(solidaction);
KConfigGroup kconfiggroup = kdestopfile.desktopGroup();
const QStringList solidwhenlist = kconfiggroup.readEntry("X-KDE-Solid-When", QStringList());
const QString solidpredicatestring = kconfiggroup.readEntry("X-KDE-Solid-Predicate");
const Solid::Predicate solidpredicate = Solid::Predicate::fromString(solidpredicatestring);
if (solidpredicate.matches(soliddevice)) {
SolidUiAction solidactionstruct;
solidactionstruct.device = soliddevice;
solidactionstruct.actions = KDesktopFileActions::userDefinedServices(solidaction, true);
solidactionstruct.when = solidwhenlist;
const Solid::Block* solidblock = soliddevice.as<Solid::Block>();
if (solidblock) {
solidactionstruct.devicenode = solidblock->device();
}
result.append(solidactionstruct);
}
}
}
return result;
}
K_PLUGIN_FACTORY(SolidUiServerFactory, registerPlugin<SolidUiServer>();)
K_EXPORT_PLUGIN(SolidUiServerFactory("soliduiserver"))
SolidUiServer::SolidUiServer(QObject* parent, const QList<QVariant>&)
: KDEDModule(parent)
{
m_soliddevices = Solid::Device::allDevices();
m_solidactions = kSolidDeviceActions();
connect(
Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)),
this, SLOT(slotDeviceAdded(QString))
@ -254,26 +282,25 @@ QString SolidUiServer::errorString(const int error)
void SolidUiServer::handleActions(const Solid::Device &soliddevice, const bool added)
{
QList<KServiceAction> kserviceactions;
const QStringList solidactions = KGlobal::dirs()->findAllResources("data", "solid/actions/");
foreach (const QString &solidaction, solidactions) {
KDesktopFile kdestopfile(solidaction);
KConfigGroup kconfiggroup = kdestopfile.desktopGroup();
const QStringList solidwhenlist = kconfiggroup.readEntry("X-KDE-Solid-When", QStringList());
SolidUiAction solidaction;
solidaction.device = soliddevice;
foreach (const SolidUiAction &solidactionstruct, m_solidactions) {
if (soliddevice.udi() != solidactionstruct.device.udi()) {
continue;
}
const QStringList solidwhenlist = solidactionstruct.when;
if (!solidwhenlist.contains(added ? s_whenadd : s_whenremove)) {
continue;
}
const QString solidpredicatestring = kconfiggroup.readEntry("X-KDE-Solid-Predicate");
const Solid::Predicate solidpredicate = Solid::Predicate::fromString(solidpredicatestring);
if (solidpredicate.matches(soliddevice)) {
kserviceactions.append(KDesktopFileActions::userDefinedServices(solidaction, true));
solidaction.actions.append(solidactionstruct.actions);
// if the UDI is different so should be the device node
solidaction.devicenode = solidactionstruct.devicenode;
}
}
if (kserviceactions.size() == 1) {
kExecuteAction(kserviceactions.first(), soliddevice, added);
} else if (!kserviceactions.isEmpty()) {
SolidUiDialog* soliddialog = new SolidUiDialog(soliddevice, kserviceactions, added);
if (solidaction.actions.size() == 1) {
kExecuteAction(solidaction.actions.first(), soliddevice, solidaction.devicenode, added);
} else if (!solidaction.actions.isEmpty()) {
SolidUiDialog* soliddialog = new SolidUiDialog(solidaction, added);
connect(soliddialog, SIGNAL(finished(int)), this, SLOT(slotDialogFinished()));
m_soliddialogs.append(soliddialog);
soliddialog->show();
@ -284,15 +311,16 @@ void SolidUiServer::handleActions(const Solid::Device &soliddevice, const bool a
void SolidUiServer::slotDeviceAdded(const QString &udi)
{
Solid::Device soliddevice(udi);
m_soliddevices.append(soliddevice);
handleActions(soliddevice, true);
m_solidactions = kSolidDeviceActions();
}
void SolidUiServer::slotDeviceRemoved(const QString &udi)
{
QMutableListIterator<Solid::Device> iter(m_soliddevices);
QMutableListIterator<SolidUiAction> iter(m_solidactions);
while (iter.hasNext()) {
Solid::Device soliddevice = iter.next();
SolidUiAction solidactionstruct = iter.next();
Solid::Device soliddevice = solidactionstruct.device;
if (soliddevice.udi() == udi) {
handleActions(soliddevice, false);
iter.remove();

View file

@ -50,7 +50,7 @@ private Q_SLOTS:
private:
void handleActions(const Solid::Device &soliddevice, const bool added);
QList<Solid::Device> m_soliddevices;
SolidUiActions m_solidactions;
QList<SolidUiDialog*> m_soliddialogs;
};

View file

@ -20,6 +20,7 @@
#define SOLIDUISERVER_COMMON_H
#include <kdebug.h>
#include <kserviceaction.h>
#include <kshell.h>
#include <ktoolinvocation.h>
#include <solid/device.h>
@ -27,6 +28,16 @@
#include <solid/storageaccess.h>
#include <solid/opticaldrive.h>
// TODO: store and update Solid::StorageAccess::filePath() reference
struct SolidUiAction
{
Solid::Device device;
QString devicenode;
QList<KServiceAction> actions;
QStringList when;
};
typedef QList<SolidUiAction> SolidUiActions;
static void kSolidMountUDI(const QString &solidudi)
{
Solid::Device soliddevice(solidudi);
@ -61,7 +72,8 @@ static void kSolidEjectUDI(const QString &solidudi)
}
// simplified version of KMacroExpander specialized for solid actions
static QStringList kSolidActionCommand(const QString &command, const Solid::Device &soliddevice, const bool mount)
static QStringList kSolidActionCommand(const QString &command, const Solid::Device &soliddevice,
const QString &solidnode, const bool mount)
{
const Solid::StorageAccess* solidstorageacces = soliddevice.as<Solid::StorageAccess>();
if (mount && solidstorageacces && !solidstorageacces->isAccessible()) {
@ -79,14 +91,8 @@ static QStringList kSolidActionCommand(const QString &command, const Solid::Devi
}
}
if (actioncommand.contains(QLatin1String("%d")) || actioncommand.contains(QLatin1String("%D"))) {
const Solid::Block* solidblock = soliddevice.as<Solid::Block>();
if (!solidblock) {
kWarning() << "device is not block" << soliddevice.udi();
} else {
const QString devicedevice = solidblock->device();
actioncommand = actioncommand.replace(QLatin1String("%d"), devicedevice);
actioncommand = actioncommand.replace(QLatin1String("%D"), devicedevice);
}
actioncommand = actioncommand.replace(QLatin1String("%d"), solidnode);
actioncommand = actioncommand.replace(QLatin1String("%D"), solidnode);
}
if (actioncommand.contains(QLatin1String("%i")) || actioncommand.contains(QLatin1String("%I"))) {
const QString deviceudi = soliddevice.udi();
@ -96,9 +102,10 @@ static QStringList kSolidActionCommand(const QString &command, const Solid::Devi
return KShell::splitArgs(actioncommand);
}
static void kExecuteAction(const KServiceAction &kserviceaction, const Solid::Device &soliddevice, const bool mount)
static void kExecuteAction(const KServiceAction &kserviceaction, const Solid::Device &soliddevice,
const QString &solidnode, const bool mount)
{
QStringList actioncommand = kSolidActionCommand(kserviceaction.exec(), soliddevice, mount);
QStringList actioncommand = kSolidActionCommand(kserviceaction.exec(), soliddevice, solidnode, mount);
if (actioncommand.size() == 0) {
kWarning() << "invalid action command" << kserviceaction.name();
return;