File indexing completed on 2024-05-05 04:59:38

0001 /*
0002  *  Main implementation for KIO-MTP
0003  *  SPDX-FileCopyrightText: 2012 Philipp Schmidt <philschmidt@gmx.net>
0004  *  SPDX-FileCopyrightText: 2018 Andreas Krutzler <andreas.krutzler@gmx.net>
0005  *  SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0006  *
0007  *  SPDX-License-Identifier: GPL-2.0-or-later
0008  */
0009 
0010 #include "kio_mtp.h"
0011 #include "kio_mtp_debug.h"
0012 
0013 // #include <KComponentData>
0014 #include <QCoreApplication>
0015 #include <QDateTime>
0016 #include <QFileInfo>
0017 #include <QTemporaryFile>
0018 #include <QTimer>
0019 
0020 #include <sys/types.h>
0021 #include <utime.h>
0022 
0023 #include "config-mtp.h"
0024 #include "kmtpdeviceinterface.h"
0025 #include "kmtpstorageinterface.h"
0026 #include "listerinterface.h"
0027 
0028 // Pseudo plugin class to embed meta data
0029 class KIOPluginForMetaData : public QObject
0030 {
0031     Q_OBJECT
0032     Q_PLUGIN_METADATA(IID "org.kde.kio.worker.mtp" FILE "mtp.json")
0033 };
0034 
0035 static UDSEntry getEntry(const KMTPDeviceInterface *device)
0036 {
0037     UDSEntry entry;
0038     entry.reserve(5);
0039     entry.fastInsert(UDSEntry::UDS_NAME, device->friendlyName());
0040     entry.fastInsert(UDSEntry::UDS_ICON_NAME, QStringLiteral("multimedia-player"));
0041     entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0042     entry.fastInsert(UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH);
0043     entry.fastInsert(UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory"));
0044     return entry;
0045 }
0046 
0047 static UDSEntry getEntry(const KMTPStorageInterface *storage)
0048 {
0049     UDSEntry entry;
0050     entry.reserve(5);
0051     entry.fastInsert(UDSEntry::UDS_NAME, storage->description());
0052     entry.fastInsert(UDSEntry::UDS_ICON_NAME, QStringLiteral("drive-removable-media"));
0053     entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0054     entry.fastInsert(UDSEntry::UDS_ACCESS, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
0055     entry.fastInsert(UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory"));
0056     return entry;
0057 }
0058 
0059 static UDSEntry getEntry(const KMTPFile &file)
0060 {
0061     UDSEntry entry;
0062     entry.reserve(9);
0063     entry.fastInsert(UDSEntry::UDS_NAME, file.filename());
0064     if (file.isFolder()) {
0065         entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0066         entry.fastInsert(UDSEntry::UDS_ACCESS, S_IRWXU | S_IRWXG | S_IRWXO);
0067     } else {
0068         entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFREG);
0069         entry.fastInsert(UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH);
0070         entry.fastInsert(UDSEntry::UDS_SIZE, file.filesize());
0071     }
0072     entry.fastInsert(UDSEntry::UDS_MIME_TYPE, file.filetype());
0073     entry.fastInsert(UDSEntry::UDS_INODE, file.itemId());
0074     entry.fastInsert(UDSEntry::UDS_ACCESS_TIME, file.modificationdate());
0075     entry.fastInsert(UDSEntry::UDS_MODIFICATION_TIME, file.modificationdate());
0076     entry.fastInsert(UDSEntry::UDS_CREATION_TIME, file.modificationdate());
0077     return entry;
0078 }
0079 
0080 static QString urlDirectory(const QUrl &url, bool appendTrailingSlash = false)
0081 {
0082     if (!appendTrailingSlash) {
0083         return url.adjusted(QUrl::StripTrailingSlash | QUrl::RemoveFilename).path();
0084     }
0085     return url.adjusted(QUrl::RemoveFilename).path();
0086 }
0087 
0088 static QString urlFileName(const QUrl &url)
0089 {
0090     return url.fileName();
0091 }
0092 
0093 static QString convertPath(const QString &workerPath)
0094 {
0095     return workerPath.section(QLatin1Char('/'), 3, -1, QString::SectionIncludeLeadingSep);
0096 }
0097 
0098 //////////////////////////////////////////////////////////////////////////////
0099 ///////////////////////////// Worker Implementation ///////////////////////////
0100 //////////////////////////////////////////////////////////////////////////////
0101 
0102 extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv)
0103 {
0104     QCoreApplication app(argc, argv);
0105     app.setApplicationName(QLatin1String("kio_mtp"));
0106 
0107     if (argc != 4) {
0108         fprintf(stderr, "Usage: kio_mtp protocol domain-socket1 domain-socket2\n");
0109         exit(-1);
0110     }
0111 
0112     MTPWorker worker(argv[2], argv[3]);
0113 
0114     worker.dispatchLoop();
0115 
0116     qCDebug(LOG_KIO_MTP) << "Worker EventLoop ended";
0117 
0118     return 0;
0119 }
0120 
0121 MTPWorker::MTPWorker(const QByteArray &pool, const QByteArray &app)
0122     : WorkerBase("mtp", pool, app)
0123 {
0124     qCDebug(LOG_KIO_MTP) << "Worker started";
0125     qCDebug(LOG_KIO_MTP) << "Connected to kiod module:" << m_kmtpDaemon.isValid();
0126 }
0127 
0128 MTPWorker::~MTPWorker()
0129 {
0130     qCDebug(LOG_KIO_MTP) << "Worker destroyed";
0131 }
0132 
0133 enum MTPWorker::Url MTPWorker::checkUrl(const QUrl &url)
0134 {
0135     if (url.path().startsWith(QLatin1String("udi="))) {
0136         const QString udi = url.adjusted(QUrl::StripTrailingSlash).path().remove(0, 4);
0137 
0138         qCDebug(LOG_KIO_MTP) << "udi = " << udi;
0139 
0140         const KMTPDeviceInterface *device = m_kmtpDaemon.deviceFromUdi(udi);
0141         if (!device) {
0142             return Url::NotFound;
0143         }
0144 
0145         QUrl newUrl;
0146         newUrl.setScheme(QStringLiteral("mtp"));
0147         newUrl.setPath(QLatin1Char('/') + device->friendlyName());
0148         redirection(newUrl);
0149 
0150         return Url::Redirected;
0151     }
0152     if (url.path().startsWith(QLatin1Char('/'))) {
0153         return Url::Valid;
0154     }
0155     if (url.scheme() == QLatin1String("mtp") && url.path().isEmpty()) {
0156         QUrl newUrl = url;
0157         newUrl.setPath(QLatin1String("/"));
0158         redirection(newUrl);
0159 
0160         return Url::Redirected;
0161     }
0162     return Url::Invalid;
0163 }
0164 
0165 WorkerResult MTPWorker::listDir(const QUrl &url)
0166 {
0167     switch (checkUrl(url)) {
0168     case Url::Valid:
0169         break;
0170     case Url::Redirected:
0171         return WorkerResult::pass();
0172     case Url::NotFound:
0173         return WorkerResult::fail(ERR_DOES_NOT_EXIST, url.path());
0174     case Url::Invalid:
0175         return WorkerResult::fail(ERR_MALFORMED_URL, url.path());
0176     }
0177 
0178     // list '.' entry, otherwise files cannot be pasted to empty folders
0179     KIO::UDSEntry entry;
0180     entry.reserve(4);
0181     entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("."));
0182     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0183     entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0);
0184     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH);
0185     listEntry(entry);
0186 
0187     const QStringList pathItems = url.path().split(QLatin1Char('/'), Qt::SkipEmptyParts);
0188     // list devices
0189     if (pathItems.isEmpty()) {
0190         qCDebug(LOG_KIO_MTP) << "Root directory, listing devices";
0191 
0192         const auto devices = m_kmtpDaemon.devices();
0193         totalSize(filesize_t(devices.size()));
0194 
0195         for (const KMTPDeviceInterface *device : devices) {
0196             listEntry(getEntry(device));
0197         }
0198 
0199         qCDebug(LOG_KIO_MTP) << "[SUCCESS] :: Devices:" << devices.size();
0200         return WorkerResult::pass();
0201     }
0202 
0203     // traverse into device
0204     KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(pathItems.first());
0205     if (!mtpDevice) {
0206         // device not found
0207         qCDebug(LOG_KIO_MTP) << "[ERROR] :: Device";
0208         return WorkerResult::fail(ERR_CANNOT_ENTER_DIRECTORY, url.path());
0209     }
0210 
0211     // list storage media
0212     if (pathItems.size() == 1) {
0213         qCDebug(LOG_KIO_MTP) << "Listing storage media for device " << pathItems.first();
0214 
0215         const auto storages = mtpDevice->storages();
0216         totalSize(filesize_t(storages.size()));
0217 
0218         if (storages.count() <= 0) {
0219             return WorkerResult::fail(ERR_WORKER_DEFINED,
0220                                       i18nc("Message shown when attempting to access an MTP device that is not fully accessible yet",
0221                                             "Could not access device. Make sure it is unlocked, and tap \"Allow\" on the popup on its screen. If that does not "
0222                                             "work, make sure MTP is enabled in its USB connection settings."));
0223         }
0224 
0225         for (KMTPStorageInterface *storage : storages) {
0226             listEntry(getEntry(storage));
0227         }
0228         qCDebug(LOG_KIO_MTP) << "[SUCCESS] :: Storage media:" << storages.count();
0229         return WorkerResult::pass();
0230     }
0231 
0232     // list files and folders
0233     const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(pathItems.at(1));
0234     if (!storage) {
0235         // storage not found
0236         qCDebug(LOG_KIO_MTP) << "[ERROR] :: Storage";
0237         return WorkerResult::fail(ERR_CANNOT_ENTER_DIRECTORY, url.path());
0238     }
0239 
0240     const QString path = convertPath(url.path());
0241 
0242 #ifdef HAVE_LIBMTP_Get_Children
0243     const auto pathVariant = storage->getFilesAndFolders2(path);
0244     if (std::holds_alternative<QDBusError>(pathVariant)) {
0245         qCWarning(LOG_KIO_MTP) << "[ERROR] :: Failed to get lister dbus path" << std::get<QDBusError>(pathVariant);
0246         return WorkerResult::fail();
0247     }
0248     Q_ASSERT(std::holds_alternative<QDBusObjectPath>(pathVariant));
0249 
0250     OrgKdeKmtpListerInterface lister(QStringLiteral("org.kde.kmtpd5"), std::get<QDBusObjectPath>(pathVariant).path(), QDBusConnection::sessionBus(), this);
0251 
0252     QEventLoop loop;
0253     connect(&lister, &OrgKdeKmtpListerInterface::entry, this, [this, &lister](const KMTPFile &file) {
0254         listEntries({getEntry(file)});
0255         if (wasKilled()) {
0256             lister.abort(); // issues finished which causes the loop to quit
0257         }
0258     });
0259     connect(&lister, &OrgKdeKmtpListerInterface::finished, &loop, &QEventLoop::quit);
0260 
0261     lister.run();
0262     loop.exec();
0263 
0264     qCDebug(LOG_KIO_MTP) << "[SUCCESS]";
0265     return WorkerResult::pass();
0266 #else
0267     int result = 0;
0268     const KMTPFileList files = storage->getFilesAndFolders(path, result);
0269 
0270     switch (result) {
0271     case 0:
0272         for (const KMTPFile &file : files) {
0273             listEntry(getEntry(file));
0274         }
0275 
0276         qCDebug(LOG_KIO_MTP) << "[SUCCESS] :: Files:" << files.count();
0277         return WorkerResult::pass();
0278     case 2:
0279         return WorkerResult::fail(ERR_IS_FILE, url.path());
0280     }
0281     // path not found
0282     return WorkerResult::fail(ERR_CANNOT_ENTER_DIRECTORY, url.path());
0283 #endif
0284 }
0285 
0286 WorkerResult MTPWorker::stat(const QUrl &url)
0287 {
0288     switch (checkUrl(url)) {
0289     case Url::Valid:
0290         break;
0291     case Url::Redirected:
0292         return WorkerResult::pass();
0293     case Url::NotFound:
0294         return WorkerResult::fail(ERR_DOES_NOT_EXIST, url.path());
0295     case Url::Invalid:
0296         return WorkerResult::fail(ERR_MALFORMED_URL, url.path());
0297     }
0298 
0299     const QStringList pathItems = url.path().split(QLatin1Char('/'), Qt::SkipEmptyParts);
0300     UDSEntry entry;
0301     // root
0302     if (pathItems.size() < 1) {
0303         entry.reserve(4);
0304         entry.fastInsert(UDSEntry::UDS_NAME, QStringLiteral("mtp:///"));
0305         entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0306         entry.fastInsert(UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH);
0307         entry.fastInsert(UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory"));
0308     } else {
0309         const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(pathItems.first());
0310         if (mtpDevice) {
0311             // device
0312             if (pathItems.size() < 2) {
0313                 entry = getEntry(mtpDevice);
0314             } else {
0315                 const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(pathItems.at(1));
0316                 if (storage) {
0317                     // storage
0318                     if (pathItems.size() < 3) {
0319                         entry = getEntry(storage);
0320                     }
0321                     // folder/file
0322                     else {
0323                         const KMTPFile file = storage->getFileMetadata(convertPath(url.path()));
0324                         if (file.isValid()) {
0325                             entry = getEntry(file);
0326                         } else {
0327                             return WorkerResult::fail(ERR_DOES_NOT_EXIST, url.path());
0328                         }
0329                     }
0330                 } else {
0331                     return WorkerResult::fail(ERR_DOES_NOT_EXIST, url.path());
0332                 }
0333             }
0334         } else {
0335             return WorkerResult::fail(ERR_DOES_NOT_EXIST, url.path());
0336         }
0337     }
0338 
0339     statEntry(entry);
0340     return WorkerResult::pass();
0341 }
0342 
0343 WorkerResult MTPWorker::mimetype(const QUrl &url)
0344 {
0345     switch (checkUrl(url)) {
0346     case Url::Valid:
0347         break;
0348     case Url::Redirected:
0349         return WorkerResult::pass();
0350     case Url::NotFound:
0351         return WorkerResult::fail(ERR_DOES_NOT_EXIST, url.path());
0352     case Url::Invalid:
0353         return WorkerResult::fail(ERR_MALFORMED_URL, url.path());
0354     }
0355 
0356     const QStringList pathItems = url.path().split(QLatin1Char('/'), Qt::SkipEmptyParts);
0357     if (pathItems.size() > 2) {
0358         const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(pathItems.first());
0359         if (mtpDevice) {
0360             const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(pathItems.at(1));
0361             if (storage) {
0362                 const KMTPFile file = storage->getFileMetadata(convertPath(url.path()));
0363                 if (file.isValid()) {
0364                     // NOTE the difference between calling mimetype and mimeType
0365                     mimeType(file.filetype());
0366                     return WorkerResult::pass();
0367                 }
0368             }
0369         }
0370     } else {
0371         mimeType(QStringLiteral("inode/directory"));
0372         return WorkerResult::pass();
0373     }
0374 
0375     return WorkerResult::fail(ERR_DOES_NOT_EXIST, url.path());
0376 }
0377 
0378 WorkerResult MTPWorker::get(const QUrl &url)
0379 {
0380     switch (checkUrl(url)) {
0381     case Url::Valid:
0382         break;
0383     case Url::Redirected:
0384     case Url::NotFound:
0385     case Url::Invalid:
0386         return WorkerResult::fail(ERR_MALFORMED_URL, url.path());
0387     }
0388 
0389     const QStringList pathItems = url.path().split(QLatin1Char('/'), Qt::SkipEmptyParts);
0390 
0391     // file
0392     if (pathItems.size() > 2) {
0393         const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(pathItems.first());
0394         if (mtpDevice) {
0395             const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(pathItems.at(1));
0396             if (storage) {
0397                 const QString path = convertPath(url.path());
0398                 const KMTPFile source = storage->getFileMetadata(path);
0399                 if (!source.isValid()) {
0400                     return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.path());
0401                 }
0402 
0403                 mimeType(source.filetype());
0404                 totalSize(source.filesize());
0405 
0406                 int result = storage->getFileToHandler(path);
0407                 if (result) {
0408                     return WorkerResult::fail(KIO::ERR_CANNOT_READ, url.path());
0409                 }
0410 
0411                 QEventLoop loop;
0412                 connect(storage, &KMTPStorageInterface::dataReady, &loop, [this](const QByteArray &data) {
0413                     MTPWorker::data(data);
0414                 });
0415                 connect(storage, &KMTPStorageInterface::copyFinished, &loop, &QEventLoop::exit);
0416                 result = loop.exec();
0417 
0418                 qCDebug(LOG_KIO_MTP) << "data received";
0419 
0420                 if (result) {
0421                     return WorkerResult::fail(ERR_CANNOT_READ, url.path());
0422                 }
0423 
0424                 data(QByteArray());
0425                 return WorkerResult::pass();
0426             }
0427         }
0428     } else {
0429         return WorkerResult::fail(ERR_UNSUPPORTED_ACTION, url.path());
0430     }
0431     return WorkerResult::fail(ERR_CANNOT_READ, url.path());
0432 }
0433 
0434 WorkerResult MTPWorker::put(const QUrl &url, int, JobFlags flags)
0435 {
0436     switch (checkUrl(url)) {
0437     case Url::Valid:
0438         break;
0439     case Url::Redirected:
0440     case Url::NotFound:
0441     case Url::Invalid:
0442         return WorkerResult::fail(ERR_MALFORMED_URL, url.path());
0443     }
0444 
0445     const QStringList destItems = url.path().split(QLatin1Char('/'), Qt::SkipEmptyParts);
0446 
0447     // can't copy to root or device, needs storage
0448     if (destItems.size() < 2) {
0449         return WorkerResult::fail(ERR_UNSUPPORTED_ACTION, url.path());
0450     }
0451 
0452     // we need to get the entire file first, then we can upload
0453     qCDebug(LOG_KIO_MTP) << "use temp file";
0454 
0455     QTemporaryFile temp;
0456     if (temp.open()) {
0457         QByteArray buffer;
0458         int len = 0;
0459 
0460         do {
0461             dataReq();
0462             len = readData(buffer);
0463             temp.write(buffer);
0464         } while (len > 0);
0465 
0466         const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(destItems.first());
0467         if (mtpDevice) {
0468             const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(destItems.at(1));
0469             if (storage) {
0470                 const QString destinationPath = convertPath(url.path());
0471 
0472                 // check if the file already exists on the device
0473                 const KMTPFile destinationFile = storage->getFileMetadata(destinationPath);
0474                 if (destinationFile.isValid()) {
0475                     if (flags & KIO::Overwrite) {
0476                         // delete existing file on the device
0477                         const int result = storage->deleteObject(destinationPath);
0478                         if (result) {
0479                             return WorkerResult::fail(ERR_CANNOT_DELETE, url.path());
0480                         }
0481                     } else {
0482                         return WorkerResult::fail(ERR_FILE_ALREADY_EXIST, url.path());
0483                     }
0484                 }
0485 
0486                 totalSize(quint64(temp.size()));
0487 
0488                 QDBusUnixFileDescriptor descriptor(temp.handle());
0489                 int result = storage->sendFileFromFileDescriptor(descriptor, destinationPath);
0490                 if (result) {
0491                     return WorkerResult::fail(KIO::ERR_CANNOT_WRITE, urlFileName(url));
0492                 }
0493 
0494                 result = waitForCopyOperation(storage);
0495                 processedSize(quint64(temp.size()));
0496                 temp.close();
0497 
0498                 switch (result) {
0499                 case 0:
0500                     qCDebug(LOG_KIO_MTP) << "data sent";
0501                     return WorkerResult::pass();
0502                 case 2:
0503                     return WorkerResult::fail(ERR_IS_FILE, urlDirectory(url));
0504                 }
0505 
0506                 return WorkerResult::fail(KIO::ERR_CANNOT_WRITE, urlFileName(url));
0507             }
0508         }
0509     }
0510 
0511     return WorkerResult::fail(KIO::ERR_CANNOT_WRITE, urlFileName(url));
0512 }
0513 
0514 WorkerResult MTPWorker::copy(const QUrl &src, const QUrl &dest, int, JobFlags flags)
0515 {
0516     if (src.scheme() == QLatin1String("mtp") && dest.scheme() == QLatin1String("mtp")) {
0517         qCDebug(LOG_KIO_MTP) << "Copy on device: Not supported";
0518         // MTP doesn't support moving files directly on the device, so we have to download and then upload...
0519 
0520         return WorkerResult::fail(ERR_UNSUPPORTED_ACTION, i18n("Cannot copy/move files on the device itself"));
0521     }
0522     if (src.scheme() == QLatin1String("file") && dest.scheme() == QLatin1String("mtp")) {
0523         // copy from filesystem to the device
0524 
0525         switch (checkUrl(dest)) {
0526         case Url::Valid:
0527             break;
0528         case Url::NotFound:
0529             return WorkerResult::fail(ERR_DOES_NOT_EXIST, src.path());
0530         case Url::Redirected:
0531         case Url::Invalid:
0532             return WorkerResult::fail(ERR_MALFORMED_URL, dest.path());
0533         }
0534 
0535         QStringList destItems = dest.path().split(QLatin1Char('/'), Qt::SkipEmptyParts);
0536 
0537         // can't copy to root or device, needs storage
0538         if (destItems.size() < 2) {
0539             return WorkerResult::fail(ERR_UNSUPPORTED_ACTION, dest.path());
0540         }
0541 
0542         qCDebug(LOG_KIO_MTP) << "Copy file " << urlFileName(src) << "from filesystem to device" << urlDirectory(src, true) << urlDirectory(dest, true);
0543 
0544         const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(destItems.first());
0545         if (mtpDevice) {
0546             const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(destItems.at(1));
0547             if (storage) {
0548                 const QString destinationPath = convertPath(dest.path());
0549 
0550                 // check if the file already exists on the device
0551                 const KMTPFile destinationFile = storage->getFileMetadata(destinationPath);
0552                 if (destinationFile.isValid()) {
0553                     if (flags & KIO::Overwrite) {
0554                         // delete existing file on the device
0555                         const int result = storage->deleteObject(destinationPath);
0556                         if (result) {
0557                             return WorkerResult::fail(ERR_CANNOT_DELETE, dest.path());
0558                         }
0559                     } else {
0560                         return WorkerResult::fail(ERR_FILE_ALREADY_EXIST, dest.path());
0561                     }
0562                 }
0563 
0564                 QFile srcFile(src.path());
0565                 if (!srcFile.open(QIODevice::ReadOnly)) {
0566                     return WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_READING, src.path());
0567                 }
0568 
0569                 qCDebug(LOG_KIO_MTP) << "Sending file" << srcFile.fileName() << "with size" << srcFile.size();
0570 
0571                 totalSize(quint64(srcFile.size()));
0572 
0573                 QDBusUnixFileDescriptor descriptor(srcFile.handle());
0574                 int result = storage->sendFileFromFileDescriptor(descriptor, destinationPath);
0575                 if (result) {
0576                     return WorkerResult::fail(KIO::ERR_CANNOT_WRITE, urlFileName(dest));
0577                 }
0578 
0579                 result = waitForCopyOperation(storage);
0580                 processedSize(quint64(srcFile.size()));
0581                 srcFile.close();
0582 
0583                 if (result) {
0584                     return WorkerResult::fail(KIO::ERR_CANNOT_WRITE, urlFileName(dest));
0585                 }
0586 
0587                 qCDebug(LOG_KIO_MTP) << "Sent file";
0588                 return WorkerResult::pass();
0589             }
0590         }
0591         return WorkerResult::fail(KIO::ERR_CANNOT_WRITE, urlFileName(src));
0592     }
0593     if (src.scheme() == QLatin1String("mtp") && dest.scheme() == QLatin1String("file")) {
0594         // copy from the device to filesystem
0595 
0596         switch (checkUrl(src)) {
0597         case Url::Valid:
0598             break;
0599         case Url::NotFound:
0600             return WorkerResult::fail(ERR_DOES_NOT_EXIST, src.toDisplayString());
0601         case Url::Redirected:
0602         case Url::Invalid:
0603             return WorkerResult::fail(ERR_MALFORMED_URL, src.toDisplayString());
0604         }
0605 
0606         qCDebug(LOG_KIO_MTP) << "Copy file" << urlFileName(src) << "from device to filesystem" << urlDirectory(src, true) << urlDirectory(dest, true);
0607 
0608         QFileInfo destination(dest.path());
0609 
0610         if (!(flags & KIO::Overwrite) && destination.exists()) {
0611             return WorkerResult::fail(ERR_FILE_ALREADY_EXIST, dest.path());
0612         }
0613 
0614         const QStringList srcItems = src.path().split(QLatin1Char('/'), Qt::SkipEmptyParts);
0615 
0616         // can't copy to root or device, needs storage
0617         if (srcItems.size() < 2) {
0618             return WorkerResult::fail(ERR_UNSUPPORTED_ACTION, src.path());
0619         }
0620 
0621         const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(srcItems.first());
0622         if (mtpDevice) {
0623             const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(srcItems.at(1));
0624             if (storage) {
0625                 QFile destFile(dest.path());
0626                 if (!destFile.open(QIODevice::Truncate | QIODevice::WriteOnly)) {
0627                     return WorkerResult::fail(KIO::ERR_WRITE_ACCESS_DENIED, dest.path());
0628                 }
0629 
0630                 const KMTPFile source = storage->getFileMetadata(convertPath(src.path()));
0631                 if (!source.isValid()) {
0632                     return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, src.path());
0633                 }
0634 
0635                 totalSize(source.filesize());
0636 
0637                 QDBusUnixFileDescriptor descriptor(destFile.handle());
0638                 int result = storage->getFileToFileDescriptor(descriptor, convertPath(src.path()));
0639                 if (result) {
0640                     return WorkerResult::fail(KIO::ERR_CANNOT_READ, urlFileName(src));
0641                 }
0642 
0643                 result = waitForCopyOperation(storage);
0644                 processedSize(quint64(source.filesize()));
0645                 destFile.close();
0646 
0647                 if (result) {
0648                     return WorkerResult::fail(KIO::ERR_CANNOT_READ, urlFileName(src));
0649                 }
0650 
0651                 // set correct modification time
0652                 struct ::utimbuf times;
0653                 times.actime = QDateTime::currentDateTime().toSecsSinceEpoch();
0654                 times.modtime = source.modificationdate();
0655 
0656                 ::utime(dest.path().toUtf8().data(), &times);
0657 
0658                 qCDebug(LOG_KIO_MTP) << "Received file";
0659                 return WorkerResult::pass();
0660             }
0661         }
0662         return WorkerResult::fail(KIO::ERR_CANNOT_READ, urlFileName(src));
0663     }
0664     return WorkerResult::fail(ERR_UNSUPPORTED_ACTION, {});
0665 }
0666 
0667 WorkerResult MTPWorker::mkdir(const QUrl &url, int)
0668 {
0669     switch (checkUrl(url)) {
0670     case Url::Valid:
0671         break;
0672     case Url::Redirected:
0673     case Url::NotFound:
0674     case Url::Invalid:
0675         return WorkerResult::fail(ERR_MALFORMED_URL, url.path());
0676     }
0677 
0678     const QStringList pathItems = url.path().split(QLatin1Char('/'), Qt::SkipEmptyParts);
0679     if (pathItems.size() > 2) {
0680         const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(pathItems.first());
0681         if (mtpDevice) {
0682             const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(pathItems.at(1));
0683             if (storage) {
0684                 // TODO: folder already exists
0685                 const quint32 itemId = storage->createFolder(convertPath(url.path()));
0686                 if (itemId) {
0687                     return WorkerResult::pass();
0688                 }
0689             }
0690         }
0691     }
0692     return WorkerResult::fail(ERR_CANNOT_MKDIR, url.path());
0693 }
0694 
0695 WorkerResult MTPWorker::del(const QUrl &url, bool)
0696 {
0697     switch (checkUrl(url)) {
0698     case Url::Valid:
0699         break;
0700     case Url::Redirected:
0701     case Url::NotFound:
0702     case Url::Invalid:
0703         return WorkerResult::fail(ERR_MALFORMED_URL, url.path());
0704     }
0705 
0706     const QStringList pathItems = url.path().split(QLatin1Char('/'), Qt::SkipEmptyParts);
0707     if (pathItems.size() >= 2) {
0708         const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(pathItems.first());
0709         if (mtpDevice) {
0710             const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(pathItems.at(1));
0711             if (storage) {
0712                 const int result = storage->deleteObject(convertPath(url.path()));
0713                 if (!result) {
0714                     return WorkerResult::pass();
0715                 }
0716             }
0717         }
0718     }
0719     return WorkerResult::fail(ERR_CANNOT_DELETE, url.path());
0720 }
0721 
0722 WorkerResult MTPWorker::rename(const QUrl &src, const QUrl &dest, JobFlags flags)
0723 {
0724     for (const auto &url : {src, dest}) {
0725         switch (checkUrl(src)) {
0726         case Url::Valid:
0727             break;
0728         case Url::Redirected:
0729         case Url::NotFound:
0730         case Url::Invalid:
0731             return WorkerResult::fail(ERR_MALFORMED_URL, url.path());
0732         }
0733     }
0734 
0735     if (src.scheme() != QLatin1String("mtp")) {
0736         // Kate: when editing files directly on the device and the user wants to save the changes,
0737         // Kate tries to move the file from /tmp/xxx to the MTP device which is not supported.
0738         // The ERR_UNSUPPORTED_ACTION error tells Kate to copy the file instead of moving.
0739         return WorkerResult::fail(ERR_UNSUPPORTED_ACTION, src.path());
0740     }
0741 
0742     const QStringList srcItems = src.path().split(QLatin1Char('/'), Qt::SkipEmptyParts);
0743     KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(srcItems.first());
0744     if (!mtpDevice) {
0745         return WorkerResult::fail(ERR_CANNOT_RENAME, src.path());
0746     }
0747 
0748     // rename Device
0749     if (srcItems.size() == 1) {
0750         if (mtpDevice->setFriendlyName(urlFileName(dest)) != 0) {
0751             return WorkerResult::fail(ERR_CANNOT_RENAME, src.path());
0752         }
0753         return WorkerResult::pass();
0754     }
0755 
0756     // rename Storage
0757     if (srcItems.size() == 2) {
0758         return WorkerResult::fail(ERR_CANNOT_RENAME, src.path());
0759     }
0760 
0761     // rename across folder boundary -> let KIO fall back to get-put instead.
0762     // There is LIBMTP_Move_Object but it's supposedly not supported on all devices. Plus get-put requires no extra code.
0763     const auto srcDir = QFileInfo(src.path()).dir().path();
0764     const auto dstDir = QFileInfo(dest.path()).dir().path();
0765     if (srcDir != dstDir) {
0766         return WorkerResult::fail(ERR_UNSUPPORTED_ACTION, src.path());
0767     }
0768 
0769     // rename file or folder
0770     const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(srcItems.at(1));
0771     if (!storage) {
0772         return WorkerResult::fail(ERR_CANNOT_RENAME, src.path());
0773     }
0774 
0775     // check if the file already exists on the device
0776     const QString destinationPath = convertPath(dest.path());
0777     const KMTPFile destinationFile = storage->getFileMetadata(destinationPath);
0778     if (destinationFile.isValid()) {
0779         if (flags & KIO::Overwrite) {
0780             // delete existing file on the device
0781             if (storage->deleteObject(destinationPath) != 0) {
0782                 return WorkerResult::fail(ERR_CANNOT_DELETE, dest.path());
0783             }
0784         } else {
0785             return WorkerResult::fail(ERR_FILE_ALREADY_EXIST, dest.path());
0786         }
0787     }
0788 
0789     if (storage->setFileName(convertPath(src.path()), dest.fileName()) != 0) {
0790         return WorkerResult::fail(ERR_CANNOT_RENAME, src.path());
0791     }
0792     return WorkerResult::pass();
0793 }
0794 
0795 WorkerResult MTPWorker::fileSystemFreeSpace(const QUrl &url)
0796 {
0797     qCDebug(LOG_KIO_MTP) << "fileSystemFreeSpace:" << url;
0798 
0799     switch (checkUrl(url)) {
0800     case Url::Valid:
0801         break;
0802     case Url::Redirected:
0803         return WorkerResult::pass();
0804     case Url::NotFound:
0805         return WorkerResult::fail(ERR_DOES_NOT_EXIST, url.path());
0806     case Url::Invalid:
0807         return WorkerResult::fail(ERR_MALFORMED_URL, url.path());
0808     }
0809 
0810     const QStringList pathItems = url.path().split(QLatin1Char('/'), Qt::SkipEmptyParts);
0811 
0812     // Storage
0813     if (pathItems.size() > 1) {
0814         const KMTPDeviceInterface *mtpDevice = m_kmtpDaemon.deviceFromName(pathItems.first());
0815         if (mtpDevice) {
0816             const KMTPStorageInterface *storage = mtpDevice->storageFromDescription(pathItems.at(1));
0817             if (storage) {
0818                 setMetaData(QStringLiteral("total"), QString::number(storage->maxCapacity()));
0819                 setMetaData(QStringLiteral("available"), QString::number(storage->freeSpaceInBytes()));
0820                 return WorkerResult::pass();
0821             }
0822         }
0823     }
0824     return WorkerResult::fail(KIO::ERR_CANNOT_STAT, url.toDisplayString());
0825 }
0826 
0827 int MTPWorker::waitForCopyOperation(const KMTPStorageInterface *storage)
0828 {
0829     QEventLoop loop;
0830     connect(storage, &KMTPStorageInterface::copyProgress, &loop, [this](qulonglong sent, qulonglong total) {
0831         Q_UNUSED(total)
0832         processedSize(sent);
0833     });
0834 
0835     // any chance to 'miss' the copyFinished signal and dead lock the worker?
0836     connect(storage, &KMTPStorageInterface::copyFinished, &loop, &QEventLoop::exit);
0837     return loop.exec();
0838 }
0839 
0840 KIO::WorkerResult MTPWorker::chmod(const QUrl &url, int permissions)
0841 {
0842     return WorkerResult::pass();
0843 }
0844 
0845 KIO::WorkerResult MTPWorker::chown(const QUrl &url, const QString &owner, const QString &group)
0846 {
0847     return WorkerResult::pass();
0848 }
0849 
0850 #include "kio_mtp.moc"
0851 #include "moc_kio_mtp.cpp"