File indexing completed on 2024-05-05 05:28:55
0001 /* 0002 * SPDX-FileCopyrightText: 2010-2012 Alejandro Fiestas Olivares <afiestas@kde.org> 0003 * SPDX-FileCopyrightText: 2010 UFO Coders <info@ufocoders.com> 0004 * SPDX-FileCopyrightText: 2014-2015 David Rosca <nowrep@gmail.com> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "kioobexftp.h" 0010 #include "bluedevil_kio_obexftp.h" 0011 #include "transferfilejob.h" 0012 #include "version.h" 0013 #include <kio_version.h> 0014 0015 #include <unistd.h> 0016 0017 #include <QCoreApplication> 0018 #include <QMimeData> 0019 #include <QMimeDatabase> 0020 #include <QTemporaryFile> 0021 0022 #include <KLocalizedString> 0023 0024 #include <BluezQt/ObexTransfer> 0025 #include <BluezQt/PendingCall> 0026 0027 // Pseudo plugin class to embed meta data 0028 class KIOPluginForMetaData : public QObject 0029 { 0030 Q_OBJECT 0031 Q_PLUGIN_METADATA(IID "org.kde.kio.slave.obexftp" FILE "obexftp.json") 0032 }; 0033 0034 extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv) 0035 { 0036 QCoreApplication app(argc, argv); 0037 0038 if (argc != 4) { 0039 fprintf(stderr, "Usage: kio_obexftp protocol domain-socket1 domain-socket2\n"); 0040 exit(-1); 0041 } 0042 0043 KioFtp slave(argv[2], argv[3]); 0044 slave.dispatchLoop(); 0045 return 0; 0046 } 0047 0048 static QString urlDirectory(const QUrl &url) 0049 { 0050 const QUrl &u = url.adjusted(QUrl::StripTrailingSlash); 0051 return u.adjusted(QUrl::RemoveFilename).path(); 0052 } 0053 0054 static QString urlFileName(const QUrl &url) 0055 { 0056 const QUrl &u = url.adjusted(QUrl::StripTrailingSlash); 0057 return u.fileName(); 0058 } 0059 0060 static QUrl urlUpDir(const QUrl &url) 0061 { 0062 const QUrl &u = url.adjusted(QUrl::StripTrailingSlash); 0063 return u.adjusted(QUrl::RemoveFilename); 0064 } 0065 0066 static bool urlIsRoot(const QUrl &url) 0067 { 0068 const QString &directory = urlDirectory(url); 0069 return (directory.isEmpty() || directory == QLatin1String("/")) && urlFileName(url).isEmpty(); 0070 } 0071 0072 KioFtp::KioFtp(const QByteArray &pool, const QByteArray &app) 0073 : WorkerBase(QByteArrayLiteral("obexftp"), pool, app) 0074 , m_transfer(nullptr) 0075 { 0076 m_kded = new org::kde::BlueDevil::ObexFtp(QStringLiteral("org.kde.kded6"), QStringLiteral("/modules/bluedevil"), QDBusConnection::sessionBus(), this); 0077 } 0078 0079 void KioFtp::connectToHost() 0080 { 0081 const QString &target = m_kded->preferredTarget(m_host); 0082 0083 if (target != QLatin1String("ftp")) { 0084 if (createSession(target)) { 0085 return; 0086 } 0087 // Fallback to ftp 0088 } 0089 0090 createSession(QStringLiteral("ftp")); 0091 } 0092 0093 KIO::WorkerResult KioFtp::testConnection() 0094 { 0095 if (!m_kded->isOnline().value()) { 0096 return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("Obexd service is not running.")); 0097 } 0098 0099 connectToHost(); 0100 0101 if (!m_transfer) { 0102 return KIO::WorkerResult::fail(KIO::ERR_CANNOT_CONNECT, m_host); 0103 } 0104 return KIO::WorkerResult::pass(); 0105 } 0106 0107 bool KioFtp::createSession(const QString &target) 0108 { 0109 QDBusPendingReply<QString> reply = m_kded->session(m_host, target); 0110 reply.waitForFinished(); 0111 0112 const QString &sessionPath = reply.value(); 0113 0114 if (reply.isError() || sessionPath.isEmpty()) { 0115 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Create session error" << reply.error().name() << reply.error().message(); 0116 delete m_transfer; 0117 m_transfer = nullptr; 0118 m_sessionPath.clear(); 0119 return false; 0120 } 0121 0122 if (m_sessionPath != sessionPath) { 0123 m_statMap.clear(); 0124 delete m_transfer; 0125 m_transfer = new BluezQt::ObexFileTransfer(QDBusObjectPath(sessionPath)); 0126 m_sessionPath = sessionPath; 0127 } 0128 0129 return true; 0130 } 0131 0132 KIO::WorkerResult KioFtp::listDir(const QUrl &url) 0133 { 0134 if (auto result = testConnection(); !result.success()) { 0135 return result; 0136 } 0137 0138 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "listdir: " << url; 0139 0140 infoMessage(i18n("Retrieving information from remote deviceā¦")); 0141 0142 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Asking for listFolder" << url.path(); 0143 0144 if (auto result = changeFolder(url.path()); !result.success()) { 0145 return result; 0146 } 0147 0148 const auto [result, entries] = listFolder(url); 0149 if (!result.success()) { 0150 return result; 0151 } 0152 0153 for (const KIO::UDSEntry &entry : entries) { 0154 listEntry(entry); 0155 } 0156 0157 return KIO::WorkerResult::pass(); 0158 } 0159 0160 KIO::WorkerResult KioFtp::copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags) 0161 { 0162 Q_UNUSED(permissions) 0163 Q_UNUSED(flags) 0164 0165 if (auto result = testConnection(); !result.success()) { 0166 return result; 0167 } 0168 0169 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "copy: " << src.url() << " to " << dest.url(); 0170 0171 return copyHelper(src, dest); 0172 } 0173 0174 KIO::WorkerResult KioFtp::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) 0175 { 0176 Q_UNUSED(src) 0177 Q_UNUSED(dest) 0178 Q_UNUSED(flags) 0179 0180 return KIO::WorkerResult::fail(KIO::ERR_UNSUPPORTED_ACTION, QString()); 0181 } 0182 0183 KIO::WorkerResult KioFtp::get(const QUrl &url) 0184 { 0185 if (auto result = testConnection(); !result.success()) { 0186 return result; 0187 } 0188 0189 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "get" << url; 0190 0191 QTemporaryFile tempFile(QStringLiteral("%1/kioftp_XXXXXX.%2").arg(QDir::tempPath(), urlFileName(url))); 0192 tempFile.open(); 0193 0194 if (auto result = copyHelper(url, QUrl::fromLocalFile(tempFile.fileName())); !result.success()) { 0195 return result; 0196 } 0197 0198 QMimeDatabase mimeDatabase; 0199 const QMimeType &mime = mimeDatabase.mimeTypeForFile(tempFile.fileName()); 0200 mimeType(mime.name()); 0201 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Mime: " << mime.name(); 0202 0203 totalSize(tempFile.size()); 0204 data(tempFile.readAll()); 0205 return KIO::WorkerResult::pass(); 0206 } 0207 0208 bool KioFtp::cancelTransfer(const QString &transfer) 0209 { 0210 return m_kded->cancelTransfer(transfer); 0211 } 0212 0213 void KioFtp::setHost(const QString &host, quint16 port, const QString &user, const QString &pass) 0214 { 0215 Q_UNUSED(port) 0216 Q_UNUSED(user) 0217 Q_UNUSED(pass) 0218 0219 m_host = host; 0220 m_host = m_host.replace(QLatin1Char('-'), QLatin1Char(':')).toUpper(); 0221 0222 infoMessage(i18n("Connecting to the device")); 0223 0224 connectToHost(); 0225 } 0226 0227 KIO::WorkerResult KioFtp::del(const QUrl &url, bool isfile) 0228 { 0229 Q_UNUSED(isfile) 0230 0231 if (auto result = testConnection(); !result.success()) { 0232 return result; 0233 } 0234 0235 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Del: " << url.url(); 0236 0237 if (auto result = changeFolder(urlDirectory(url)); !result.success()) { 0238 return result; 0239 } 0240 0241 if (auto result = deleteFile(urlFileName(url)); !result.success()) { 0242 return result; 0243 } 0244 0245 return KIO::WorkerResult::pass(); 0246 } 0247 0248 KIO::WorkerResult KioFtp::mkdir(const QUrl &url, int permissions) 0249 { 0250 Q_UNUSED(permissions) 0251 0252 if (auto result = testConnection(); !result.success()) { 0253 return result; 0254 } 0255 0256 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "MkDir: " << url.url(); 0257 0258 if (auto result = changeFolder(urlDirectory(url)); !result.success()) { 0259 return result; 0260 } 0261 0262 if (auto result = createFolder(urlFileName(url)); !result.success()) { 0263 return result; 0264 } 0265 0266 return KIO::WorkerResult::pass(); 0267 } 0268 0269 KIO::WorkerResult KioFtp::stat(const QUrl &url) 0270 { 0271 if (auto result = testConnection(); !result.success()) { 0272 return result; 0273 } 0274 0275 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat: " << url.url(); 0276 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat Dir: " << urlDirectory(url); 0277 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat File: " << urlFileName(url); 0278 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Empty Dir: " << urlDirectory(url).isEmpty(); 0279 0280 return statHelper(url); 0281 } 0282 0283 KIO::WorkerResult KioFtp::copyHelper(const QUrl &src, const QUrl &dest) 0284 { 0285 if (src.scheme() == QLatin1String("obexftp") && dest.scheme() == QLatin1String("obexftp")) { 0286 return copyWithinObexftp(src, dest); 0287 } 0288 0289 if (src.scheme() == QLatin1String("obexftp")) { 0290 return copyFromObexftp(src, dest); 0291 } 0292 0293 if (dest.scheme() == QLatin1String("obexftp")) { 0294 return copyToObexftp(src, dest); 0295 } 0296 0297 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "This shouldn't happen..."; 0298 return KIO::WorkerResult::fail(KIO::ERR_UNKNOWN, i18n("This should not happen")); 0299 } 0300 0301 KIO::WorkerResult KioFtp::copyWithinObexftp(const QUrl &src, const QUrl &dest) 0302 { 0303 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Source: " << src << "Dest:" << dest; 0304 0305 if (auto result = changeFolder(urlDirectory(src)); !result.success()) { 0306 return result; 0307 } 0308 0309 BluezQt::PendingCall *call = m_transfer->copyFile(src.path(), dest.path()); 0310 call->waitForFinished(); 0311 0312 if (call->error()) { 0313 // Copying files within obexftp is currently not implemented in obexd 0314 if (call->errorText() == QLatin1String("Not Implemented")) { 0315 return KIO::WorkerResult::fail(KIO::ERR_UNSUPPORTED_ACTION, src.path()); 0316 } else { 0317 return KIO::WorkerResult::fail(KIO::ERR_CANNOT_WRITE, src.path()); 0318 } 0319 } 0320 0321 return KIO::WorkerResult::pass(); 0322 } 0323 0324 KIO::WorkerResult KioFtp::copyFromObexftp(const QUrl &src, const QUrl &dest) 0325 { 0326 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Source: " << src << "Dest:" << dest; 0327 0328 if (auto result = changeFolder(urlDirectory(src)); !result.success()) { 0329 return result; 0330 } 0331 0332 if (!m_statMap.contains(src.toDisplayString())) { 0333 auto [result, _] = listFolder(urlUpDir(src)); 0334 if (!result.success()) { 0335 return result; 0336 } 0337 } 0338 0339 BluezQt::PendingCall *call = m_transfer->getFile(dest.path(), urlFileName(src)); 0340 call->waitForFinished(); 0341 0342 int size = m_statMap.value(src.toDisplayString()).numberValue(KIO::UDSEntry::UDS_SIZE); 0343 totalSize(size); 0344 0345 BluezQt::ObexTransferPtr transfer = call->value().value<BluezQt::ObexTransferPtr>(); 0346 TransferFileJob *getFile = new TransferFileJob(transfer, this); 0347 getFile->exec(); 0348 0349 return KIO::WorkerResult::pass(); 0350 } 0351 0352 KIO::WorkerResult KioFtp::copyToObexftp(const QUrl &src, const QUrl &dest) 0353 { 0354 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Source:" << src << "Dest:" << dest; 0355 0356 if (auto result = changeFolder(urlDirectory(dest)); !result.success()) { 0357 return result; 0358 } 0359 0360 BluezQt::PendingCall *call = m_transfer->putFile(src.path(), urlFileName(dest)); 0361 call->waitForFinished(); 0362 0363 int size = QFile(src.path()).size(); 0364 totalSize(size); 0365 0366 BluezQt::ObexTransferPtr transfer = call->value().value<BluezQt::ObexTransferPtr>(); 0367 TransferFileJob *putFile = new TransferFileJob(transfer, this); 0368 putFile->exec(); 0369 0370 return KIO::WorkerResult::pass(); 0371 } 0372 0373 KIO::WorkerResult KioFtp::statHelper(const QUrl &url) 0374 { 0375 if (m_statMap.contains(url.toDisplayString())) { 0376 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "statMap contains the url"; 0377 statEntry(m_statMap.value(url.toDisplayString())); 0378 return KIO::WorkerResult::pass(); 0379 } 0380 0381 if (urlIsRoot(url)) { 0382 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Url is root"; 0383 KIO::UDSEntry entry; 0384 entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("/")); 0385 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); 0386 entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0700); 0387 0388 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Adding stat cache" << url.toDisplayString(); 0389 m_statMap.insert(url.toDisplayString(), entry); 0390 statEntry(entry); 0391 return KIO::WorkerResult::pass(); 0392 } 0393 0394 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "statMap does not contains the url"; 0395 0396 if (auto result = changeFolder(urlDirectory(url)); !result.success()) { 0397 return result; 0398 } 0399 0400 auto [result, _] = listFolder(urlUpDir(url)); 0401 if (!result.success()) { 0402 return result; 0403 } 0404 0405 if (!m_statMap.contains(url.toDisplayString())) { 0406 qCWarning(BLUEDEVIL_KIO_OBEXFTP_LOG) << "statMap still does not contains the url!"; 0407 } 0408 0409 statEntry(m_statMap.value(url.toDisplayString())); 0410 0411 return KIO::WorkerResult::pass(); 0412 } 0413 0414 KioFtp::ListResult KioFtp::listFolder(const QUrl &url) 0415 { 0416 QList<KIO::UDSEntry> list; 0417 0418 BluezQt::PendingCall *call = m_transfer->listFolder(); 0419 call->waitForFinished(); 0420 0421 if (call->error()) { 0422 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "List folder error" << call->errorText(); 0423 return {KIO::WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_READING, url.path()), {}}; 0424 } 0425 0426 const QList<BluezQt::ObexFileTransferEntry> &items = call->value().value<QList<BluezQt::ObexFileTransferEntry>>(); 0427 0428 for (const BluezQt::ObexFileTransferEntry &item : items) { 0429 if (!item.isValid()) { 0430 continue; 0431 } 0432 0433 KIO::UDSEntry entry; 0434 entry.fastInsert(KIO::UDSEntry::UDS_NAME, item.name()); 0435 entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, item.label()); 0436 entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0700); 0437 entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, item.modificationTime().toSecsSinceEpoch()); 0438 entry.fastInsert(KIO::UDSEntry::UDS_SIZE, item.size()); 0439 0440 if (item.type() == BluezQt::ObexFileTransferEntry::Folder) { 0441 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); 0442 } else if (item.type() == BluezQt::ObexFileTransferEntry::File) { 0443 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); 0444 } 0445 0446 if (urlIsRoot(url)) { 0447 updateRootEntryIcon(entry, item.memoryType()); 0448 } 0449 0450 list.append(entry); 0451 0452 // Most probably the client of the kio will stat each file 0453 // so since we are on it, let's cache all of them. 0454 QUrl statUrl = url; 0455 0456 if (statUrl.path().endsWith('/')) { 0457 statUrl.setPath(statUrl.path() + item.name()); 0458 } else { 0459 statUrl.setPath(statUrl.path() + QLatin1Char('/') + item.name()); 0460 } 0461 0462 if (!m_statMap.contains(statUrl.toDisplayString())) { 0463 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat:" << statUrl.toDisplayString() << entry.stringValue(KIO::UDSEntry::UDS_NAME) 0464 << entry.numberValue(KIO::UDSEntry::UDS_SIZE); 0465 m_statMap.insert(statUrl.toDisplayString(), entry); 0466 } 0467 } 0468 0469 return {KIO::WorkerResult::pass(), list}; 0470 } 0471 0472 KIO::WorkerResult KioFtp::changeFolder(const QString &folder) 0473 { 0474 BluezQt::PendingCall *call = m_transfer->changeFolder(folder); 0475 call->waitForFinished(); 0476 0477 if (call->error()) { 0478 return KIO::WorkerResult::fail(KIO::ERR_CANNOT_ENTER_DIRECTORY, folder); 0479 } 0480 return KIO::WorkerResult::pass(); 0481 } 0482 0483 KIO::WorkerResult KioFtp::createFolder(const QString &folder) 0484 { 0485 BluezQt::PendingCall *call = m_transfer->createFolder(folder); 0486 call->waitForFinished(); 0487 0488 if (call->error()) { 0489 return KIO::WorkerResult::fail(KIO::ERR_CANNOT_MKDIR, folder); 0490 } 0491 return KIO::WorkerResult::pass(); 0492 } 0493 0494 KIO::WorkerResult KioFtp::deleteFile(const QString &file) 0495 { 0496 BluezQt::PendingCall *call = m_transfer->deleteFile(file); 0497 call->waitForFinished(); 0498 0499 if (call->error()) { 0500 return KIO::WorkerResult::fail(KIO::ERR_CANNOT_DELETE, file); 0501 } 0502 return KIO::WorkerResult::pass(); 0503 } 0504 0505 void KioFtp::updateRootEntryIcon(KIO::UDSEntry &entry, const QString &memoryType) 0506 { 0507 const QString &path = entry.stringValue(KIO::UDSEntry::UDS_NAME); 0508 0509 // Nokia (mount-points are C: D: E: ...) 0510 if (path.size() == 2 && path.at(1) == QLatin1Char(':')) { 0511 if (memoryType.startsWith(QLatin1String("DEV"))) { 0512 entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("drive-removable-media")); 0513 } else if (memoryType == QLatin1String("MMC")) { 0514 entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("media-flash-sd-mmc")); 0515 } 0516 } 0517 0518 // Android 0519 if (entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1String("PHONE_MEMORY")) { 0520 entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Phone memory")); 0521 entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("drive-removable-media")); 0522 } else if (entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1String("EXTERNAL_MEMORY")) { 0523 entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("External memory")); 0524 entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("media-flash-sd-mmc")); 0525 } 0526 } 0527 0528 #include "kioobexftp.moc" 0529 0530 #include "moc_kioobexftp.cpp"