From 470b52d29c5c5a8d6ab5bd842145e076f017899d Mon Sep 17 00:00:00 2001 From: Ivailo Monev Date: Sat, 9 Sep 2023 10:56:55 +0300 Subject: [PATCH] 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 --- plasma/runners/solid/solidrunner.cpp | 7 ++- soliduiserver/CMakeLists.txt | 13 +++- soliduiserver/actions/CMakeLists.txt | 4 +- soliduiserver/actions/unmountdevice.desktop | 10 ++++ soliduiserver/solid_unmount_device.cpp | 58 ++++++++++++++++++ soliduiserver/soliduidialog.cpp | 18 +++--- soliduiserver/soliduidialog.h | 9 ++- soliduiserver/soliduiserver.cpp | 66 +++++++++++++++------ soliduiserver/soliduiserver.h | 2 +- soliduiserver/soliduiserver_common.h | 29 +++++---- 10 files changed, 166 insertions(+), 50 deletions(-) create mode 100644 soliduiserver/actions/unmountdevice.desktop create mode 100644 soliduiserver/solid_unmount_device.cpp diff --git a/plasma/runners/solid/solidrunner.cpp b/plasma/runners/solid/solidrunner.cpp index 61935d70..90f02cad 100644 --- a/plasma/runners/solid/solidrunner.cpp +++ b/plasma/runners/solid/solidrunner.cpp @@ -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(); + QStringList actioncommand = kSolidActionCommand( + kserviceaction.exec(), soliddevice, + solidblock ? solidblock->device() : QString(), + true + ); if (actioncommand.size() == 0) { kWarning() << "invalid action command" << actionname << "in" << actionfilepath; return; diff --git a/soliduiserver/CMakeLists.txt b/soliduiserver/CMakeLists.txt index 3f1aa8d9..3a068b4f 100644 --- a/soliduiserver/CMakeLists.txt +++ b/soliduiserver/CMakeLists.txt @@ -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} ) diff --git a/soliduiserver/actions/CMakeLists.txt b/soliduiserver/actions/CMakeLists.txt index fc133ab2..f07d1480 100644 --- a/soliduiserver/actions/CMakeLists.txt +++ b/soliduiserver/actions/CMakeLists.txt @@ -1,4 +1,6 @@ install( - FILES openinwindow.desktop + FILES + openinwindow.desktop + unmountdevice.desktop DESTINATION ${KDE4_DATA_INSTALL_DIR}/solid/actions ) diff --git a/soliduiserver/actions/unmountdevice.desktop b/soliduiserver/actions/unmountdevice.desktop new file mode 100644 index 00000000..e19b3a62 --- /dev/null +++ b/soliduiserver/actions/unmountdevice.desktop @@ -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 diff --git a/soliduiserver/solid_unmount_device.cpp b/soliduiserver/solid_unmount_device.cpp new file mode 100644 index 00000000..9e52d504 --- /dev/null +++ b/soliduiserver/solid_unmount_device.cpp @@ -0,0 +1,58 @@ +/* This file is part of the KDE project + Copyright (C) 2023 Ivailo Monev + + 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 +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/soliduiserver/soliduidialog.cpp b/soliduiserver/soliduidialog.cpp index bac3e72b..af29840c 100644 --- a/soliduiserver/soliduidialog.cpp +++ b/soliduiserver/soliduidialog.cpp @@ -24,13 +24,11 @@ #include #include -SolidUiDialog::SolidUiDialog(const Solid::Device &soliddevice, - const QList &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" diff --git a/soliduiserver/soliduidialog.h b/soliduiserver/soliduidialog.h index e07b1247..decad862 100644 --- a/soliduiserver/soliduidialog.h +++ b/soliduiserver/soliduidialog.h @@ -19,6 +19,8 @@ #ifndef SOLIDUIDIALOG_H #define SOLIDUIDIALOG_H +#include "soliduiserver_common.h" + #include #include #include @@ -31,9 +33,7 @@ class SolidUiDialog : public KDialog { Q_OBJECT public: - SolidUiDialog(const Solid::Device &soliddevce, - const QList &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 m_serviceactions; + SolidUiAction m_solidaction; bool m_mount; QWidget* m_mainwidget; QGridLayout* m_mainlayout; diff --git a/soliduiserver/soliduiserver.cpp b/soliduiserver/soliduiserver.cpp index d3362f6a..b1536917 100644 --- a/soliduiserver/soliduiserver.cpp +++ b/soliduiserver/soliduiserver.cpp @@ -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 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(); + if (solidblock) { + solidactionstruct.devicenode = solidblock->device(); + } + result.append(solidactionstruct); + } + } + } + return result; +} + K_PLUGIN_FACTORY(SolidUiServerFactory, registerPlugin();) K_EXPORT_PLUGIN(SolidUiServerFactory("soliduiserver")) SolidUiServer::SolidUiServer(QObject* parent, const QList&) : 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 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 iter(m_soliddevices); + QMutableListIterator 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(); diff --git a/soliduiserver/soliduiserver.h b/soliduiserver/soliduiserver.h index 7dcc56d6..fbd376d3 100644 --- a/soliduiserver/soliduiserver.h +++ b/soliduiserver/soliduiserver.h @@ -50,7 +50,7 @@ private Q_SLOTS: private: void handleActions(const Solid::Device &soliddevice, const bool added); - QList m_soliddevices; + SolidUiActions m_solidactions; QList m_soliddialogs; }; diff --git a/soliduiserver/soliduiserver_common.h b/soliduiserver/soliduiserver_common.h index d2479ac7..adf7fe7b 100644 --- a/soliduiserver/soliduiserver_common.h +++ b/soliduiserver/soliduiserver_common.h @@ -20,6 +20,7 @@ #define SOLIDUISERVER_COMMON_H #include +#include #include #include #include @@ -27,6 +28,16 @@ #include #include +// TODO: store and update Solid::StorageAccess::filePath() reference +struct SolidUiAction +{ + Solid::Device device; + QString devicenode; + QList actions; + QStringList when; +}; +typedef QList 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(); 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(); - 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;