File indexing completed on 2024-04-14 15:32:03

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