File indexing completed on 2024-05-12 09:51:34
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"