File indexing completed on 2024-04-21 05:25:58

0001 /*
0002  *  SPDX-FileCopyrightText: 2013 Alejandro Fiestas Fiestas <afiestas@kde.org>
0003  *  SPDX-FileCopyrightText: 2014-2015 David Rosca <nowrep@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "receivefilejob.h"
0009 #include "bluedevil_kded.h"
0010 #include "filereceiversettings.h"
0011 #include "obexagent.h"
0012 
0013 #include <QDir>
0014 #include <QIcon>
0015 #include <QTemporaryFile>
0016 #include <QTimer>
0017 
0018 #include <KIO/CopyJob>
0019 #include <KIO/JobTracker>
0020 #include <KJobTrackerInterface>
0021 #include <KLocalizedString>
0022 #include <KNotification>
0023 
0024 #include <BluezQt/Adapter>
0025 #include <BluezQt/Device>
0026 #include <BluezQt/Manager>
0027 #include <BluezQt/ObexSession>
0028 
0029 ReceiveFileJob::ReceiveFileJob(const BluezQt::Request<QString> &req, BluezQt::ObexTransferPtr transfer, BluezQt::ObexSessionPtr session, ObexAgent *parent)
0030     : KJob(parent)
0031     , m_speedBytes(0)
0032     , m_agent(parent)
0033     , m_transfer(transfer)
0034     , m_session(session)
0035     , m_request(req)
0036 {
0037     setCapabilities(Killable);
0038     // Pretend to be BlueDevil's file transfer agent even though we're run inside kded
0039     setProperty("desktopFileName", QStringLiteral("org.kde.bluedevilsendfile"));
0040     setProperty("immediateProgressReporting", true);
0041 }
0042 
0043 QString ReceiveFileJob::deviceAddress() const
0044 {
0045     return m_deviceAddress;
0046 }
0047 
0048 void ReceiveFileJob::start()
0049 {
0050     QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection);
0051 }
0052 
0053 bool ReceiveFileJob::doKill()
0054 {
0055     qCDebug(BLUEDEVIL_KDED_LOG) << "ReceiveFileJob-Kill";
0056     m_transfer->cancel();
0057     return true;
0058 }
0059 
0060 void ReceiveFileJob::init()
0061 {
0062     qCDebug(BLUEDEVIL_KDED_LOG) << "ReceiveFileJob:";
0063     qCDebug(BLUEDEVIL_KDED_LOG) << "\tName:" << m_transfer->name();
0064     qCDebug(BLUEDEVIL_KDED_LOG) << "\tFilename:" << m_transfer->fileName();
0065     qCDebug(BLUEDEVIL_KDED_LOG) << "\tStatus:" << m_transfer->status();
0066     qCDebug(BLUEDEVIL_KDED_LOG) << "\tType:" << m_transfer->type();
0067     qCDebug(BLUEDEVIL_KDED_LOG) << "\tSize:" << m_transfer->size();
0068     qCDebug(BLUEDEVIL_KDED_LOG) << "\tTransferred:" << m_transfer->transferred();
0069 
0070     qCDebug(BLUEDEVIL_KDED_LOG) << "ObexSession:";
0071     qCDebug(BLUEDEVIL_KDED_LOG) << "\tSource:" << m_session->source();
0072     qCDebug(BLUEDEVIL_KDED_LOG) << "\tDestination:" << m_session->destination();
0073 
0074     connect(m_transfer.data(), &BluezQt::ObexTransfer::statusChanged, this, &ReceiveFileJob::statusChanged);
0075     connect(m_transfer.data(), &BluezQt::ObexTransfer::transferredChanged, this, &ReceiveFileJob::transferredChanged);
0076 
0077     m_deviceName = m_session->destination();
0078 
0079     BluezQt::AdapterPtr adapter = m_agent->manager()->adapterForAddress(m_session->source());
0080     if (!adapter) {
0081         qCDebug(BLUEDEVIL_KDED_LOG) << "No adapter for" << m_session->source();
0082         showNotification();
0083         return;
0084     }
0085 
0086     BluezQt::DevicePtr device = adapter->deviceForAddress(m_session->destination());
0087     if (!device) {
0088         qCDebug(BLUEDEVIL_KDED_LOG) << "No device for" << m_session->destination();
0089         showNotification();
0090         return;
0091     }
0092 
0093     m_deviceName = device->name();
0094     m_deviceAddress = device->address();
0095 
0096     if (m_agent->shouldAutoAcceptTransfer(m_deviceAddress)) {
0097         slotAccept();
0098         return;
0099     }
0100 
0101     FileReceiverSettings::self()->load();
0102     switch (FileReceiverSettings::self()->autoAccept()) {
0103     case 0: // Never auto-accept transfers
0104         showNotification();
0105         break;
0106 
0107     case 1: // Auto-accept only from trusted devices
0108         if (device->isTrusted()) {
0109             qCDebug(BLUEDEVIL_KDED_LOG) << "Auto-accepting transfer for trusted device";
0110             slotAccept();
0111         } else {
0112             showNotification();
0113         }
0114         break;
0115 
0116     case 2: // Auto-accept all transfers
0117         qCDebug(BLUEDEVIL_KDED_LOG) << "Auto-accepting transfers for all devices";
0118         slotAccept();
0119         break;
0120 
0121     default: // Unknown
0122         showNotification();
0123         break;
0124     }
0125 }
0126 
0127 void ReceiveFileJob::showNotification()
0128 {
0129     KNotification *notification = new KNotification(QStringLiteral("IncomingFile"), KNotification::Persistent, this);
0130 
0131     notification->setTitle(QStringLiteral("%1 (%2)").arg(m_deviceName.toHtmlEscaped(), m_deviceAddress));
0132     notification->setText(i18nc("Show a notification asking to authorize or deny an incoming file transfer to this computer from a Bluetooth device.",
0133                                 "%1 is sending you the file %2",
0134                                 m_deviceName.toHtmlEscaped(),
0135                                 m_transfer->name()));
0136 
0137     auto acceptAction =
0138         notification->addAction(i18nc("Button to accept the incoming file transfer and download it in the default download directory", "Accept"));
0139     auto cancelAction = notification->addAction(i18nc("Deny the incoming file transfer", "Cancel"));
0140 
0141     connect(acceptAction, &KNotificationAction::activated, this, &ReceiveFileJob::slotAccept);
0142     connect(cancelAction, &KNotificationAction::activated, this, &ReceiveFileJob::slotCancel);
0143     connect(notification, &KNotification::closed, this, &ReceiveFileJob::slotCancel);
0144 
0145     notification->setComponentName(QStringLiteral("bluedevil"));
0146 
0147     notification->sendEvent();
0148 }
0149 
0150 void ReceiveFileJob::slotAccept()
0151 {
0152     qCDebug(BLUEDEVIL_KDED_LOG) << "ReceiveFileJob-Accept";
0153 
0154     KIO::getJobTracker()->registerJob(this);
0155 
0156     FileReceiverSettings::self()->load();
0157     m_targetPath = FileReceiverSettings::self()->saveUrl().adjusted(QUrl::StripTrailingSlash);
0158     m_targetPath.setPath(m_targetPath.path() + QLatin1Char('/') + m_transfer->name());
0159 
0160     setTotalAmount(Files, 1);
0161 
0162     Q_EMIT description(this,
0163                        i18nc("@title job", "Receiving file"),
0164                        QPair<QString, QString>(i18nc("File transfer origin", "From"), m_deviceName),
0165                        QPair<QString, QString>(i18nc("File transfer destination", "To"), m_targetPath.toDisplayString()));
0166 
0167     m_tempPath = createTempPath(m_transfer->name());
0168     qCDebug(BLUEDEVIL_KDED_LOG) << "TempPath" << m_tempPath;
0169 
0170     m_accepted = true;
0171     m_request.accept(m_tempPath);
0172 }
0173 
0174 void ReceiveFileJob::slotCancel()
0175 {
0176     if (!m_accepted && m_transfer->status() == BluezQt::ObexTransfer::Queued) {
0177         qCDebug(BLUEDEVIL_KDED_LOG) << "Cancel Push";
0178         m_request.reject();
0179         setError(KJob::UserDefinedError);
0180         emitResult();
0181     }
0182 }
0183 
0184 void ReceiveFileJob::moveFinished(KJob *job)
0185 {
0186     if (job->error()) {
0187         qCDebug(BLUEDEVIL_KDED_LOG) << job->error();
0188         qCDebug(BLUEDEVIL_KDED_LOG) << job->errorText();
0189         setError(job->error());
0190         setErrorText(i18n("Saving file failed"));
0191 
0192         QFile::remove(m_tempPath);
0193     }
0194 
0195     setProcessedAmount(Files, 1);
0196 
0197     emitResult();
0198 }
0199 
0200 void ReceiveFileJob::statusChanged(BluezQt::ObexTransfer::Status status)
0201 {
0202     switch (status) {
0203     case BluezQt::ObexTransfer::Active:
0204         qCDebug(BLUEDEVIL_KDED_LOG) << "ReceiveFileJob-Transfer Active";
0205         setTotalAmount(Bytes, m_transfer->size());
0206         setProcessedAmount(Bytes, 0);
0207         m_time = QTime::currentTime();
0208         break;
0209 
0210     case BluezQt::ObexTransfer::Complete: {
0211         qCDebug(BLUEDEVIL_KDED_LOG) << "ReceiveFileJob-Transfer Complete";
0212         KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(m_tempPath), m_targetPath, KIO::HideProgressInfo);
0213         job->setUiDelegate(nullptr);
0214         connect(job, &KIO::CopyJob::finished, this, &ReceiveFileJob::moveFinished);
0215         break;
0216     }
0217 
0218     case BluezQt::ObexTransfer::Error:
0219         qCDebug(BLUEDEVIL_KDED_LOG) << "ReceiveFileJob-Transfer Error";
0220         setError(KJob::UserDefinedError);
0221         setErrorText(i18n("Bluetooth transfer failed"));
0222 
0223         // Delay emitResult to make sure notification is displayed even
0224         // when transfer errors right after accepting it
0225         QTimer::singleShot(500, this, [this]() {
0226             emitResult();
0227         });
0228         break;
0229 
0230     default:
0231         qCDebug(BLUEDEVIL_KDED_LOG) << "Not implemented status: " << status;
0232         break;
0233     }
0234 }
0235 
0236 void ReceiveFileJob::transferredChanged(quint64 transferred)
0237 {
0238     // qCDebug(BLUEDEVIL_KDED_LOG) << "ReceiveFileJob-Transferred" << transferred;
0239 
0240     // If at least 1 second has passed since last update
0241     int secondsSinceLastTime = m_time.secsTo(QTime::currentTime());
0242     if (secondsSinceLastTime > 0) {
0243         unsigned long speed = (transferred - m_speedBytes) / secondsSinceLastTime;
0244         emitSpeed(speed);
0245 
0246         m_time = QTime::currentTime();
0247         m_speedBytes = transferred;
0248     }
0249 
0250     setProcessedAmount(Bytes, transferred);
0251 }
0252 
0253 QString ReceiveFileJob::createTempPath(const QString &fileName) const
0254 {
0255     QString xdgCacheHome = QFile::decodeName(qgetenv("XDG_CACHE_HOME"));
0256     if (xdgCacheHome.isEmpty()) {
0257         xdgCacheHome = QDir::homePath() + QStringLiteral("/.cache");
0258     }
0259 
0260     xdgCacheHome.append(QLatin1String("/obexd/"));
0261     QString path = xdgCacheHome + fileName;
0262 
0263     int i = 0;
0264     while (QFile::exists(path)) {
0265         path = xdgCacheHome + fileName + QString::number(i);
0266         i++;
0267     }
0268 
0269     return path;
0270 }
0271 
0272 #include "moc_receivefilejob.cpp"