File indexing completed on 2024-04-28 05:45:41

0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0002 // SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0003 
0004 #include <chrono>
0005 #include <optional>
0006 
0007 #include <QDBusConnection>
0008 #include <QDBusMessage>
0009 #include <QDBusPendingReply>
0010 #include <QDebug>
0011 
0012 #include <KIO/WorkerBase>
0013 #include <KIO/WorkerFactory>
0014 
0015 #include "dbustypes.h"
0016 #include "interface_chmodcommand.h"
0017 #include "interface_chowncommand.h"
0018 #include "interface_copycommand.h"
0019 #include "interface_delcommand.h"
0020 #include "interface_file.h"
0021 #include "interface_getcommand.h"
0022 #include "interface_listdircommand.h"
0023 #include "interface_mkdircommand.h"
0024 #include "interface_putcommand.h"
0025 #include "interface_renamecommand.h"
0026 #include "interface_statcommand.h"
0027 
0028 using namespace KIO;
0029 using namespace std::chrono_literals;
0030 
0031 namespace
0032 {
0033 constexpr auto killPollInterval = 200ms;
0034 } // namespace
0035 
0036 class AdminWorker : public QObject, public WorkerBase
0037 {
0038     Q_OBJECT
0039 public:
0040     using WorkerBase::WorkerBase;
0041 
0042     static QString serviceName()
0043     {
0044         return QStringLiteral("org.kde.kio.admin");
0045     }
0046 
0047     static QString servicePath()
0048     {
0049         return QStringLiteral("/");
0050     }
0051 
0052     static QString serviceInterface()
0053     {
0054         return QStringLiteral("org.kde.kio.admin");
0055     }
0056 
0057     // Start the eventloop but check every couple milliseconds if the worker was
0058     // aborted, if that is the case quit the loop.
0059     void execLoop(QEventLoop &loop)
0060     {
0061         QTimer timer;
0062         timer.setInterval(killPollInterval);
0063         timer.setSingleShot(false);
0064         connect(&timer, &QTimer::timeout, &timer, [this, &loop] {
0065             if (wasKilled()) {
0066                 loop.quit();
0067             }
0068         });
0069         timer.start();
0070         loop.exec();
0071     }
0072 
0073     // Variant of execLoop which additionally will forward the kill order to
0074     // the command object. This allows us to cancel long running operations
0075     // such as get().
0076     template<typename Iface>
0077     void execLoopWithTerminatingIface(QEventLoop &loop, Iface &iface)
0078     {
0079         QTimer timer;
0080         timer.setInterval(killPollInterval);
0081         timer.setSingleShot(false);
0082         connect(
0083             &timer,
0084             &QTimer::timeout,
0085             &timer,
0086             [this, &loop, &iface] {
0087                 if (wasKilled()) {
0088                     iface.kill();
0089                     loop.quit();
0090                 }
0091             },
0092             Qt::QueuedConnection);
0093         timer.start();
0094         loop.exec();
0095     }
0096 
0097     [[nodiscard]] WorkerResult toFailure(const QDBusMessage &msg)
0098     {
0099         qWarning() << msg.errorName() << msg.errorMessage();
0100         switch (QDBusError(msg).type()) {
0101         case QDBusError::AccessDenied:
0102             return WorkerResult::fail(ERR_ACCESS_DENIED, msg.errorMessage());
0103         default:
0104             break;
0105         }
0106         return WorkerResult::fail();
0107     }
0108 
0109     WorkerResult listDir(const QUrl &url) override
0110     {
0111         auto request = QDBusMessage::createMethodCall(serviceName(), servicePath(), serviceInterface(), QStringLiteral("listDir"));
0112         request << url.toString();
0113         auto reply = QDBusConnection::systemBus().call(request);
0114         if (reply.type() == QDBusMessage::ErrorMessage) {
0115             return toFailure(reply);
0116         }
0117         const auto path = reply.arguments().at(0).value<QDBusObjectPath>().path();
0118         qDebug() << path;
0119 
0120         OrgKdeKioAdminListDirCommandInterface iface(serviceName(), path, QDBusConnection::systemBus(), this);
0121         connect(&iface, &OrgKdeKioAdminListDirCommandInterface::result, this, &AdminWorker::result);
0122 
0123         QDBusConnection::systemBus().connect(serviceName(),
0124                                              path,
0125                                              QStringLiteral("org.kde.kio.admin.ListDirCommand"),
0126                                              QStringLiteral("entries"),
0127                                              this,
0128                                              SLOT(entries(KIO::UDSEntryList)));
0129 
0130         iface.start();
0131 
0132         execLoopWithTerminatingIface(loop, iface);
0133 
0134         QDBusConnection::systemBus().disconnect(serviceName(),
0135                                                 path,
0136                                                 QStringLiteral("org.kde.kio.admin.ListDirCommand"),
0137                                                 QStringLiteral("entries"),
0138                                                 this,
0139                                                 SLOT(entries(KIO::UDSEntryList)));
0140         return m_result;
0141     }
0142 
0143     WorkerResult open(const QUrl &url, QIODevice::OpenMode mode) override
0144     {
0145         qDebug() << Q_FUNC_INFO;
0146         auto request = QDBusMessage::createMethodCall(serviceName(), servicePath(), serviceInterface(), QStringLiteral("file"));
0147         request << url.toString() << (int)mode;
0148         auto reply = QDBusConnection::systemBus().call(request);
0149         if (reply.type() == QDBusMessage::ErrorMessage) {
0150             return toFailure(reply);
0151         }
0152         const auto path = reply.arguments().at(0).value<QDBusObjectPath>().path();
0153 
0154         m_file = std::make_unique<OrgKdeKioAdminFileInterface>(serviceName(), path, QDBusConnection::systemBus(), this);
0155         connect(m_file.get(), &OrgKdeKioAdminFileInterface::opened, this, [this] {
0156             result(0, {});
0157         });
0158         connect(m_file.get(), &OrgKdeKioAdminFileInterface::written, this, [this](qulonglong length) {
0159             written(length);
0160             Q_ASSERT(m_pendingWrite.has_value());
0161             m_pendingWrite.emplace(m_pendingWrite.value() - length);
0162             if (m_pendingWrite.value() == 0) {
0163                 loop.quit();
0164             }
0165             result(0, {});
0166         });
0167         connect(m_file.get(), &OrgKdeKioAdminFileInterface::data, this, [this](const QByteArray &blob) {
0168             data(blob);
0169             loop.quit();
0170             result(0, {});
0171         });
0172         connect(m_file.get(), &OrgKdeKioAdminFileInterface::positionChanged, this, [this](qulonglong offset) {
0173             position(offset);
0174             loop.quit();
0175             result(0, {});
0176         });
0177         connect(m_file.get(), &OrgKdeKioAdminFileInterface::truncated, this, [this](qulonglong length) {
0178             truncated(length);
0179             loop.quit();
0180             result(0, {});
0181         });
0182         connect(m_file.get(), &OrgKdeKioAdminFileInterface::mimeTypeFound, this, [this](const QString &mimetype) {
0183             mimeType(mimetype);
0184             loop.quit();
0185             result(0, {});
0186         });
0187         connect(m_file.get(), &OrgKdeKioAdminFileInterface::result, this, &AdminWorker::result);
0188         m_file->open();
0189 
0190         execLoop(loop);
0191         return m_result;
0192     }
0193 
0194     WorkerResult read(KIO::filesize_t size) override
0195     {
0196         qDebug() << Q_FUNC_INFO;
0197         m_file->read(size);
0198         execLoop(loop);
0199         return m_result;
0200     }
0201 
0202     WorkerResult write(const QByteArray &data) override
0203     {
0204         qDebug() << Q_FUNC_INFO;
0205         Q_ASSERT(!m_pendingWrite.has_value());
0206         m_pendingWrite = data.size();
0207         m_file->write(data);
0208         execLoop(loop);
0209         return m_result;
0210     }
0211 
0212     WorkerResult seek(KIO::filesize_t offset) override
0213     {
0214         qDebug() << Q_FUNC_INFO;
0215         m_file->seek(offset);
0216         execLoop(loop);
0217         return m_result;
0218     }
0219 
0220     WorkerResult truncate(KIO::filesize_t size) override
0221     {
0222         qDebug() << Q_FUNC_INFO;
0223         m_file->truncate(size);
0224         execLoop(loop);
0225         return m_result;
0226     }
0227 
0228     WorkerResult close() override
0229     {
0230         qDebug() << Q_FUNC_INFO;
0231         m_file->close();
0232         execLoop(loop);
0233         return m_result;
0234     }
0235 
0236     WorkerResult put(const QUrl &url, int permissions, JobFlags flags) override
0237     {
0238         auto request = QDBusMessage::createMethodCall(serviceName(), servicePath(), serviceInterface(), QStringLiteral("put"));
0239         request << url.toString() << permissions << static_cast<int>(flags);
0240         auto reply = QDBusConnection::systemBus().call(request);
0241         if (reply.type() == QDBusMessage::ErrorMessage) {
0242             return toFailure(reply);
0243         }
0244         const auto path = reply.arguments().at(0).value<QDBusObjectPath>().path();
0245 
0246         OrgKdeKioAdminPutCommandInterface iface(serviceName(), path, QDBusConnection::systemBus(), this);
0247 
0248         connect(&iface, &OrgKdeKioAdminPutCommandInterface::dataRequest, this, [this, &iface]() {
0249             dataReq();
0250             QByteArray buffer;
0251             if (const int read = readData(buffer); read < 0) {
0252                 qWarning() << "Failed to read data for unknown reason" << read;
0253             }
0254             iface.data(buffer);
0255         });
0256         connect(&iface, &OrgKdeKioAdminPutCommandInterface::result, this, &AdminWorker::result);
0257         iface.start();
0258 
0259         execLoopWithTerminatingIface(loop, iface);
0260         return m_result;
0261     }
0262 
0263     WorkerResult stat(const QUrl &url) override
0264     {
0265         auto request = QDBusMessage::createMethodCall(serviceName(), servicePath(), serviceInterface(), QStringLiteral("stat"));
0266         request << url.toString();
0267         auto reply = QDBusConnection::systemBus().call(request);
0268         if (reply.type() == QDBusMessage::ErrorMessage) {
0269             return toFailure(reply);
0270         }
0271         const auto path = reply.arguments().at(0).value<QDBusObjectPath>().path();
0272 
0273         QDBusConnection::systemBus()
0274             .connect(serviceName(), path, QStringLiteral("org.kde.kio.admin.StatCommand"), QStringLiteral("statEntry"), this, SLOT(entry(KIO::UDSEntry)));
0275 
0276         OrgKdeKioAdminStatCommandInterface iface(serviceName(), path, QDBusConnection::systemBus(), this);
0277         connect(&iface, &OrgKdeKioAdminStatCommandInterface::result, this, &AdminWorker::result);
0278         iface.start();
0279 
0280         QDBusConnection::systemBus().call(
0281             QDBusMessage::createMethodCall(serviceName(), path, QStringLiteral("org.kde.kio.admin.StatCommand"), QStringLiteral("start")));
0282 
0283         execLoop(loop);
0284 
0285         QDBusConnection::systemBus()
0286             .disconnect(serviceName(), path, QStringLiteral("org.kde.kio.admin.StatCommand"), QStringLiteral("statEntry"), this, SLOT(entry(KIO::UDSEntry)));
0287 
0288         return m_result;
0289     }
0290 
0291     WorkerResult copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags) override
0292     {
0293         qDebug() << Q_FUNC_INFO;
0294         auto request = QDBusMessage::createMethodCall(serviceName(), servicePath(), serviceInterface(), QStringLiteral("copy"));
0295         request << src.toString() << dest.toString() << permissions << static_cast<int>(flags);
0296         auto reply = QDBusConnection::systemBus().call(request);
0297         if (reply.type() == QDBusMessage::ErrorMessage) {
0298             return toFailure(reply);
0299         }
0300         const auto path = reply.arguments().at(0).value<QDBusObjectPath>().path();
0301         qDebug() << path;
0302 
0303         OrgKdeKioAdminCopyCommandInterface iface(serviceName(), path, QDBusConnection::systemBus(), this);
0304         connect(&iface, &OrgKdeKioAdminCopyCommandInterface::result, this, &AdminWorker::result);
0305         iface.start();
0306 
0307         execLoop(loop);
0308         return m_result;
0309     }
0310 
0311     WorkerResult get(const QUrl &url) override
0312     {
0313         qDebug() << Q_FUNC_INFO;
0314         auto request = QDBusMessage::createMethodCall(serviceName(), servicePath(), serviceInterface(), QStringLiteral("get"));
0315         request << url.toString();
0316         auto reply = QDBusConnection::systemBus().call(request);
0317         if (reply.type() == QDBusMessage::ErrorMessage) {
0318             return toFailure(reply);
0319         }
0320         const auto path = reply.arguments().at(0).value<QDBusObjectPath>().path();
0321         qDebug() << path;
0322 
0323         OrgKdeKioAdminGetCommandInterface iface(serviceName(), path, QDBusConnection::systemBus(), this);
0324         connect(&iface, &OrgKdeKioAdminGetCommandInterface::data, this, [this](const QByteArray &blob) {
0325             data(blob);
0326         });
0327         connect(&iface, &OrgKdeKioAdminGetCommandInterface::mimeTypeFound, this, [this](const QString &mimetype) {
0328             mimeType(mimetype);
0329         });
0330         connect(&iface, &OrgKdeKioAdminGetCommandInterface::result, this, &AdminWorker::result);
0331         iface.start();
0332 
0333         execLoopWithTerminatingIface(loop, iface);
0334         return m_result;
0335     }
0336 
0337     WorkerResult del(const QUrl &url, bool isFile) override
0338     {
0339         Q_UNUSED(isFile);
0340 
0341         qDebug() << Q_FUNC_INFO;
0342         auto request = QDBusMessage::createMethodCall(serviceName(), servicePath(), serviceInterface(), QStringLiteral("del"));
0343         request << url.toString();
0344         auto reply = QDBusConnection::systemBus().call(request);
0345         if (reply.type() == QDBusMessage::ErrorMessage) {
0346             return toFailure(reply);
0347         }
0348         const auto path = reply.arguments().at(0).value<QDBusObjectPath>().path();
0349 
0350         OrgKdeKioAdminDelCommandInterface iface(serviceName(), path, QDBusConnection::systemBus(), this);
0351         connect(&iface, &OrgKdeKioAdminDelCommandInterface::result, this, &AdminWorker::result);
0352         iface.start();
0353 
0354         execLoop(loop);
0355         return m_result;
0356     }
0357 
0358     WorkerResult mkdir(const QUrl &url, int permissions) override
0359     {
0360         qDebug() << Q_FUNC_INFO;
0361         auto request = QDBusMessage::createMethodCall(serviceName(), servicePath(), serviceInterface(), QStringLiteral("mkdir"));
0362         request << url.toString() << permissions;
0363         auto reply = QDBusConnection::systemBus().call(request);
0364         if (reply.type() == QDBusMessage::ErrorMessage) {
0365             return toFailure(reply);
0366         }
0367         const auto path = reply.arguments().at(0).value<QDBusObjectPath>().path();
0368 
0369         OrgKdeKioAdminMkdirCommandInterface iface(serviceName(), path, QDBusConnection::systemBus(), this);
0370         connect(&iface, &OrgKdeKioAdminMkdirCommandInterface::result, this, &AdminWorker::result);
0371         iface.start();
0372 
0373         execLoop(loop);
0374         return m_result;
0375     }
0376 
0377     WorkerResult rename(const QUrl &src, const QUrl &dest, JobFlags flags) override
0378     {
0379         qDebug() << Q_FUNC_INFO;
0380         auto request = QDBusMessage::createMethodCall(serviceName(), servicePath(), serviceInterface(), QStringLiteral("rename"));
0381         request << src.toString() << dest.toString() << static_cast<int>(flags);
0382         auto reply = QDBusConnection::systemBus().call(request);
0383         if (reply.type() == QDBusMessage::ErrorMessage) {
0384             return toFailure(reply);
0385         }
0386         const auto path = reply.arguments().at(0).value<QDBusObjectPath>().path();
0387 
0388         OrgKdeKioAdminRenameCommandInterface iface(serviceName(), path, QDBusConnection::systemBus(), this);
0389         connect(&iface, &OrgKdeKioAdminRenameCommandInterface::result, this, &AdminWorker::result);
0390         iface.start();
0391 
0392         execLoop(loop);
0393         return m_result;
0394     }
0395 
0396     //  WorkerResult symlink(const QString &target, const QUrl &dest, JobFlags flags) override;
0397 
0398     WorkerResult chmod(const QUrl &url, int permissions) override
0399     {
0400         qDebug() << Q_FUNC_INFO;
0401         auto request = QDBusMessage::createMethodCall(serviceName(), servicePath(), serviceInterface(), QStringLiteral("chmod"));
0402         request << url.toString() << permissions;
0403         auto reply = QDBusConnection::systemBus().call(request);
0404         if (reply.type() == QDBusMessage::ErrorMessage) {
0405             return toFailure(reply);
0406         }
0407         const auto path = reply.arguments().at(0).value<QDBusObjectPath>().path();
0408 
0409         OrgKdeKioAdminChmodCommandInterface iface(serviceName(), path, QDBusConnection::systemBus(), this);
0410         connect(&iface, &OrgKdeKioAdminChmodCommandInterface::result, this, &AdminWorker::result);
0411         iface.start();
0412 
0413         execLoop(loop);
0414         return m_result;
0415     }
0416 
0417     WorkerResult chown(const QUrl &url, const QString &owner, const QString &group) override
0418     {
0419         qDebug() << Q_FUNC_INFO;
0420         auto request = QDBusMessage::createMethodCall(serviceName(), servicePath(), serviceInterface(), QStringLiteral("chown"));
0421         request << url.toString() << owner << group;
0422         auto reply = QDBusConnection::systemBus().call(request);
0423         if (reply.type() == QDBusMessage::ErrorMessage) {
0424             return toFailure(reply);
0425         }
0426         const auto path = reply.arguments().at(0).value<QDBusObjectPath>().path();
0427 
0428         OrgKdeKioAdminChownCommandInterface iface(serviceName(), path, QDBusConnection::systemBus(), this);
0429         connect(&iface, &OrgKdeKioAdminChownCommandInterface::result, this, &AdminWorker::result);
0430         iface.start();
0431 
0432         execLoop(loop);
0433         return m_result;
0434     }
0435 
0436     // WorkerResult setModificationTime(const QUrl &url, const QDateTime &mtime) override
0437     // {
0438     //     qDebug() << Q_FUNC_INFO;
0439     //     return WorkerResult::pass();
0440     // }
0441 
0442 private Q_SLOTS:
0443     void entry(const KIO::UDSEntry &entry)
0444     {
0445         qDebug() << Q_FUNC_INFO << entry;
0446         statEntry(entry);
0447     }
0448 
0449     void entries(const KIO::UDSEntryList &list)
0450     {
0451         qDebug() << Q_FUNC_INFO;
0452         listEntries(list);
0453     }
0454 
0455     void result(int error, const QString &errorString)
0456     {
0457         qDebug() << "RESULT" << error << errorString;
0458         if (error != KJob::NoError) {
0459             m_result = WorkerResult::fail(error, errorString);
0460         } else {
0461             m_result = WorkerResult::pass();
0462         }
0463         loop.quit();
0464     }
0465 
0466 private:
0467     WorkerResult m_result = WorkerResult::pass();
0468     std::unique_ptr<OrgKdeKioAdminFileInterface> m_file;
0469     QEventLoop loop;
0470     std::optional<quint64> m_pendingWrite = std::nullopt;
0471 };
0472 
0473 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0474 class KIOPluginFactory : public KIO::RealWorkerFactory
0475 {
0476     Q_OBJECT
0477     Q_PLUGIN_METADATA(IID "org.kde.kio.worker.admin" FILE "admin.json")
0478 public:
0479     std::unique_ptr<KIO::SlaveBase> createWorker(const QByteArray &pool, const QByteArray &app) override
0480     {
0481         Q_UNUSED(pool);
0482         Q_UNUSED(app);
0483         return {};
0484     }
0485 
0486     std::unique_ptr<KIO::WorkerBase> createRealWorker(const QByteArray &pool, const QByteArray &app) override
0487     {
0488         qRegisterMetaType<KIO::UDSEntryList>("KIO::UDSEntryList");
0489         qDBusRegisterMetaType<KIO::UDSEntryList>();
0490         qRegisterMetaType<KIO::UDSEntry>("KIO::UDSEntry");
0491         qDBusRegisterMetaType<KIO::UDSEntry>();
0492         return std::make_unique<AdminWorker>(QByteArrayLiteral("admin"), pool, app);
0493     }
0494 };
0495 #else
0496 
0497 class KIOPluginFactory : public KIO::WorkerFactory
0498 {
0499     Q_OBJECT
0500     Q_PLUGIN_METADATA(IID "org.kde.kio.worker.admin" FILE "admin.json")
0501 public:
0502     std::unique_ptr<KIO::WorkerBase> createWorker(const QByteArray &pool, const QByteArray &app) override
0503     {
0504         qRegisterMetaType<KIO::UDSEntryList>("KIO::UDSEntryList");
0505         qDBusRegisterMetaType<KIO::UDSEntryList>();
0506         qRegisterMetaType<KIO::UDSEntry>("KIO::UDSEntry");
0507         qDBusRegisterMetaType<KIO::UDSEntry>();
0508         return std::make_unique<AdminWorker>(QByteArrayLiteral("admin"), pool, app);
0509     }
0510 };
0511 
0512 #endif
0513 
0514 #include "worker.moc"