File indexing completed on 2024-05-05 17:32:40
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 : SlaveBase(QByteArrayLiteral("obexftp"), pool, app) 0074 , m_transfer(nullptr) 0075 { 0076 m_kded = new org::kde::BlueDevil::ObexFtp(QStringLiteral("org.kde.kded5"), 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 bool KioFtp::testConnection() 0094 { 0095 if (!m_kded->isOnline().value()) { 0096 #if KIO_VERSION >= QT_VERSION_CHECK(5, 96, 0) 0097 error(KIO::ERR_WORKER_DEFINED, i18n("Obexd service is not running.")); 0098 #else 0099 error(KIO::ERR_SLAVE_DEFINED, i18n("Obexd service is not running.")); 0100 #endif 0101 return false; 0102 } 0103 0104 connectToHost(); 0105 0106 if (!m_transfer) { 0107 error(KIO::ERR_CANNOT_CONNECT, m_host); 0108 return false; 0109 } 0110 return true; 0111 } 0112 0113 bool KioFtp::createSession(const QString &target) 0114 { 0115 QDBusPendingReply<QString> reply = m_kded->session(m_host, target); 0116 reply.waitForFinished(); 0117 0118 const QString &sessionPath = reply.value(); 0119 0120 if (reply.isError() || sessionPath.isEmpty()) { 0121 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Create session error" << reply.error().name() << reply.error().message(); 0122 delete m_transfer; 0123 m_transfer = nullptr; 0124 m_sessionPath.clear(); 0125 return false; 0126 } 0127 0128 if (m_sessionPath != sessionPath) { 0129 m_statMap.clear(); 0130 delete m_transfer; 0131 m_transfer = new BluezQt::ObexFileTransfer(QDBusObjectPath(sessionPath)); 0132 m_sessionPath = sessionPath; 0133 } 0134 0135 return true; 0136 } 0137 0138 void KioFtp::listDir(const QUrl &url) 0139 { 0140 if (!testConnection()) { 0141 return; 0142 } 0143 0144 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "listdir: " << url; 0145 0146 infoMessage(i18n("Retrieving information from remote deviceā¦")); 0147 0148 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Asking for listFolder" << url.path(); 0149 0150 if (!changeFolder(url.path())) { 0151 return; 0152 } 0153 0154 bool ok; 0155 const QList<KIO::UDSEntry> &list = listFolder(url, &ok); 0156 if (!ok) { 0157 return; 0158 } 0159 0160 for (const KIO::UDSEntry &entry : list) { 0161 listEntry(entry); 0162 } 0163 0164 finished(); 0165 } 0166 0167 void KioFtp::copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags) 0168 { 0169 Q_UNUSED(permissions) 0170 Q_UNUSED(flags) 0171 0172 if (!testConnection()) { 0173 return; 0174 } 0175 0176 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "copy: " << src.url() << " to " << dest.url(); 0177 0178 copyHelper(src, dest); 0179 0180 finished(); 0181 } 0182 0183 void KioFtp::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) 0184 { 0185 Q_UNUSED(src) 0186 Q_UNUSED(dest) 0187 Q_UNUSED(flags) 0188 0189 error(KIO::ERR_UNSUPPORTED_ACTION, QString()); 0190 } 0191 0192 void KioFtp::get(const QUrl &url) 0193 { 0194 if (!testConnection()) { 0195 return; 0196 } 0197 0198 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "get" << url; 0199 0200 QTemporaryFile tempFile(QStringLiteral("%1/kioftp_XXXXXX.%2").arg(QDir::tempPath(), urlFileName(url))); 0201 tempFile.open(); 0202 0203 copyHelper(url, QUrl::fromLocalFile(tempFile.fileName())); 0204 0205 QMimeDatabase mimeDatabase; 0206 const QMimeType &mime = mimeDatabase.mimeTypeForFile(tempFile.fileName()); 0207 mimeType(mime.name()); 0208 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Mime: " << mime.name(); 0209 0210 totalSize(tempFile.size()); 0211 data(tempFile.readAll()); 0212 finished(); 0213 } 0214 0215 bool KioFtp::cancelTransfer(const QString &transfer) 0216 { 0217 return m_kded->cancelTransfer(transfer); 0218 } 0219 0220 void KioFtp::setHost(const QString &host, quint16 port, const QString &user, const QString &pass) 0221 { 0222 Q_UNUSED(port) 0223 Q_UNUSED(user) 0224 Q_UNUSED(pass) 0225 0226 m_host = host; 0227 m_host = m_host.replace(QLatin1Char('-'), QLatin1Char(':')).toUpper(); 0228 0229 infoMessage(i18n("Connecting to the device")); 0230 0231 connectToHost(); 0232 } 0233 0234 void KioFtp::del(const QUrl &url, bool isfile) 0235 { 0236 Q_UNUSED(isfile) 0237 0238 if (!testConnection()) { 0239 return; 0240 } 0241 0242 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Del: " << url.url(); 0243 0244 if (!changeFolder(urlDirectory(url))) { 0245 return; 0246 } 0247 0248 if (!deleteFile(urlFileName(url))) { 0249 return; 0250 } 0251 0252 finished(); 0253 } 0254 0255 void KioFtp::mkdir(const QUrl &url, int permissions) 0256 { 0257 Q_UNUSED(permissions) 0258 0259 if (!testConnection()) { 0260 return; 0261 } 0262 0263 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "MkDir: " << url.url(); 0264 0265 if (!changeFolder(urlDirectory(url))) { 0266 return; 0267 } 0268 0269 if (!createFolder(urlFileName(url))) { 0270 return; 0271 } 0272 0273 finished(); 0274 } 0275 0276 void KioFtp::stat(const QUrl &url) 0277 { 0278 if (!testConnection()) { 0279 return; 0280 } 0281 0282 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat: " << url.url(); 0283 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat Dir: " << urlDirectory(url); 0284 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat File: " << urlFileName(url); 0285 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Empty Dir: " << urlDirectory(url).isEmpty(); 0286 0287 statHelper(url); 0288 0289 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Finished"; 0290 finished(); 0291 } 0292 0293 void KioFtp::copyHelper(const QUrl &src, const QUrl &dest) 0294 { 0295 if (src.scheme() == QLatin1String("obexftp") && dest.scheme() == QLatin1String("obexftp")) { 0296 copyWithinObexftp(src, dest); 0297 return; 0298 } 0299 0300 if (src.scheme() == QLatin1String("obexftp")) { 0301 copyFromObexftp(src, dest); 0302 return; 0303 } 0304 0305 if (dest.scheme() == QLatin1String("obexftp")) { 0306 copyToObexftp(src, dest); 0307 return; 0308 } 0309 0310 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "This shouldn't happen..."; 0311 } 0312 0313 void KioFtp::copyWithinObexftp(const QUrl &src, const QUrl &dest) 0314 { 0315 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Source: " << src << "Dest:" << dest; 0316 0317 if (!changeFolder(urlDirectory(src))) { 0318 return; 0319 } 0320 0321 BluezQt::PendingCall *call = m_transfer->copyFile(src.path(), dest.path()); 0322 call->waitForFinished(); 0323 0324 if (call->error()) { 0325 // Copying files within obexftp is currently not implemented in obexd 0326 if (call->errorText() == QLatin1String("Not Implemented")) { 0327 error(KIO::ERR_UNSUPPORTED_ACTION, src.path()); 0328 } else { 0329 error(KIO::ERR_CANNOT_WRITE, src.path()); 0330 } 0331 return; 0332 } 0333 0334 finished(); 0335 } 0336 0337 void KioFtp::copyFromObexftp(const QUrl &src, const QUrl &dest) 0338 { 0339 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Source: " << src << "Dest:" << dest; 0340 0341 if (!changeFolder(urlDirectory(src))) { 0342 return; 0343 } 0344 0345 if (!m_statMap.contains(src.toDisplayString())) { 0346 bool ok; 0347 listFolder(urlUpDir(src), &ok); 0348 } 0349 0350 BluezQt::PendingCall *call = m_transfer->getFile(dest.path(), urlFileName(src)); 0351 call->waitForFinished(); 0352 0353 int size = m_statMap.value(src.toDisplayString()).numberValue(KIO::UDSEntry::UDS_SIZE); 0354 totalSize(size); 0355 0356 BluezQt::ObexTransferPtr transfer = call->value().value<BluezQt::ObexTransferPtr>(); 0357 TransferFileJob *getFile = new TransferFileJob(transfer, this); 0358 getFile->exec(); 0359 } 0360 0361 void KioFtp::copyToObexftp(const QUrl &src, const QUrl &dest) 0362 { 0363 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Source:" << src << "Dest:" << dest; 0364 0365 if (!changeFolder(urlDirectory(dest))) { 0366 return; 0367 } 0368 0369 BluezQt::PendingCall *call = m_transfer->putFile(src.path(), urlFileName(dest)); 0370 call->waitForFinished(); 0371 0372 int size = QFile(src.path()).size(); 0373 totalSize(size); 0374 0375 BluezQt::ObexTransferPtr transfer = call->value().value<BluezQt::ObexTransferPtr>(); 0376 TransferFileJob *putFile = new TransferFileJob(transfer, this); 0377 putFile->exec(); 0378 } 0379 0380 void KioFtp::statHelper(const QUrl &url) 0381 { 0382 if (m_statMap.contains(url.toDisplayString())) { 0383 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "statMap contains the url"; 0384 statEntry(m_statMap.value(url.toDisplayString())); 0385 return; 0386 } 0387 0388 if (urlIsRoot(url)) { 0389 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Url is root"; 0390 KIO::UDSEntry entry; 0391 entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("/")); 0392 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); 0393 entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0700); 0394 0395 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Adding stat cache" << url.toDisplayString(); 0396 m_statMap.insert(url.toDisplayString(), entry); 0397 statEntry(entry); 0398 return; 0399 } 0400 0401 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "statMap does not contains the url"; 0402 0403 if (!changeFolder(urlDirectory(url))) { 0404 return; 0405 } 0406 0407 bool ok; 0408 listFolder(urlUpDir(url), &ok); 0409 if (!ok) { 0410 return; 0411 } 0412 0413 if (!m_statMap.contains(url.toDisplayString())) { 0414 qCWarning(BLUEDEVIL_KIO_OBEXFTP_LOG) << "statMap still does not contains the url!"; 0415 } 0416 0417 statEntry(m_statMap.value(url.toDisplayString())); 0418 } 0419 0420 QList<KIO::UDSEntry> KioFtp::listFolder(const QUrl &url, bool *ok) 0421 { 0422 QList<KIO::UDSEntry> list; 0423 0424 BluezQt::PendingCall *call = m_transfer->listFolder(); 0425 call->waitForFinished(); 0426 0427 if (call->error()) { 0428 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "List folder error" << call->errorText(); 0429 error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.path()); 0430 *ok = false; 0431 return list; 0432 } 0433 0434 const QList<BluezQt::ObexFileTransferEntry> &items = call->value().value<QList<BluezQt::ObexFileTransferEntry>>(); 0435 0436 for (const BluezQt::ObexFileTransferEntry &item : items) { 0437 if (!item.isValid()) { 0438 continue; 0439 } 0440 0441 KIO::UDSEntry entry; 0442 entry.fastInsert(KIO::UDSEntry::UDS_NAME, item.name()); 0443 entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, item.label()); 0444 entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0700); 0445 entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, item.modificationTime().toSecsSinceEpoch()); 0446 entry.fastInsert(KIO::UDSEntry::UDS_SIZE, item.size()); 0447 0448 if (item.type() == BluezQt::ObexFileTransferEntry::Folder) { 0449 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); 0450 } else if (item.type() == BluezQt::ObexFileTransferEntry::File) { 0451 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); 0452 } 0453 0454 if (urlIsRoot(url)) { 0455 updateRootEntryIcon(entry, item.memoryType()); 0456 } 0457 0458 list.append(entry); 0459 0460 // Most probably the client of the kio will stat each file 0461 // so since we are on it, let's cache all of them. 0462 QUrl statUrl = url; 0463 0464 if (statUrl.path().endsWith('/')) { 0465 statUrl.setPath(statUrl.path() + item.name()); 0466 } else { 0467 statUrl.setPath(statUrl.path() + QLatin1Char('/') + item.name()); 0468 } 0469 0470 if (!m_statMap.contains(statUrl.toDisplayString())) { 0471 qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat:" << statUrl.toDisplayString() << entry.stringValue(KIO::UDSEntry::UDS_NAME) 0472 << entry.numberValue(KIO::UDSEntry::UDS_SIZE); 0473 m_statMap.insert(statUrl.toDisplayString(), entry); 0474 } 0475 } 0476 0477 *ok = true; 0478 return list; 0479 } 0480 0481 bool KioFtp::changeFolder(const QString &folder) 0482 { 0483 BluezQt::PendingCall *call = m_transfer->changeFolder(folder); 0484 call->waitForFinished(); 0485 0486 if (call->error()) { 0487 error(KIO::ERR_CANNOT_ENTER_DIRECTORY, folder); 0488 return false; 0489 } 0490 return true; 0491 } 0492 0493 bool KioFtp::createFolder(const QString &folder) 0494 { 0495 BluezQt::PendingCall *call = m_transfer->createFolder(folder); 0496 call->waitForFinished(); 0497 0498 if (call->error()) { 0499 error(KIO::ERR_CANNOT_MKDIR, folder); 0500 return false; 0501 } 0502 return true; 0503 } 0504 0505 bool KioFtp::deleteFile(const QString &file) 0506 { 0507 BluezQt::PendingCall *call = m_transfer->deleteFile(file); 0508 call->waitForFinished(); 0509 0510 if (call->error()) { 0511 error(KIO::ERR_CANNOT_DELETE, file); 0512 return false; 0513 } 0514 return true; 0515 } 0516 0517 void KioFtp::updateRootEntryIcon(KIO::UDSEntry &entry, const QString &memoryType) 0518 { 0519 const QString &path = entry.stringValue(KIO::UDSEntry::UDS_NAME); 0520 0521 // Nokia (mount-points are C: D: E: ...) 0522 if (path.size() == 2 && path.at(1) == QLatin1Char(':')) { 0523 if (memoryType.startsWith(QLatin1String("DEV"))) { 0524 entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("drive-removable-media")); 0525 } else if (memoryType == QLatin1String("MMC")) { 0526 entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("media-flash-sd-mmc")); 0527 } 0528 } 0529 0530 // Android 0531 if (entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1String("PHONE_MEMORY")) { 0532 entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Phone memory")); 0533 entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("drive-removable-media")); 0534 } else if (entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1String("EXTERNAL_MEMORY")) { 0535 entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("External memory")); 0536 entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("media-flash-sd-mmc")); 0537 } 0538 } 0539 0540 #include "kioobexftp.moc"