File indexing completed on 2024-05-19 08:50:29
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(), ×); 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"