| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtBluetooth module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| |
| #include "qbluetoothtransferreply_bluez_p.h" |
| #include "qbluetoothaddress.h" |
| |
| #include "bluez/obex_client_p.h" |
| #include "bluez/obex_agent_p.h" |
| #include "bluez/obex_transfer_p.h" |
| #include "bluez/bluez5_helper_p.h" |
| #include "bluez/obex_client1_bluez5_p.h" |
| #include "bluez/obex_objectpush1_bluez5_p.h" |
| #include "bluez/obex_transfer1_bluez5_p.h" |
| #include "bluez/properties_p.h" |
| #include "qbluetoothtransferreply.h" |
| |
| #include <QtCore/QAtomicInt> |
| #include <QtCore/QLoggingCategory> |
| #include <QtCore/QVector> |
| #include <QFuture> |
| #include <QFutureWatcher> |
| #include <QtConcurrentRun> |
| |
| static const QLatin1String agentPath("/qt/agent"); |
| static QAtomicInt agentPathCounter; |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) |
| |
| QBluetoothTransferReplyBluez::QBluetoothTransferReplyBluez(QIODevice *input, const QBluetoothTransferRequest &request, |
| QBluetoothTransferManager *parent) |
| : QBluetoothTransferReply(parent), |
| m_source(input), |
| m_running(false), m_finished(false), m_size(0), |
| m_error(QBluetoothTransferReply::NoError), m_errorStr(), m_transfer_path() |
| { |
| setRequest(request); |
| setManager(parent); |
| |
| if (!input) { |
| qCWarning(QT_BT_BLUEZ) << "Invalid input device (null)"; |
| m_errorStr = QBluetoothTransferReply::tr("Invalid input device (null)"); |
| m_error = QBluetoothTransferReply::FileNotFoundError; |
| m_finished = true; |
| return; |
| } |
| |
| if (isBluez5()) { |
| m_clientBluez = new OrgBluezObexClient1Interface(QStringLiteral("org.bluez.obex"), |
| QStringLiteral("/org/bluez/obex"), |
| QDBusConnection::sessionBus(), this); |
| |
| |
| } else { |
| m_client = new OrgOpenobexClientInterface(QStringLiteral("org.openobex.client"), |
| QStringLiteral("/"), |
| QDBusConnection::sessionBus()); |
| |
| m_agent_path = agentPath; |
| m_agent_path.append(QStringLiteral("/%1%2/%3"). |
| arg(sanitizeNameForDBus(QCoreApplication::applicationName())). |
| arg(QCoreApplication::applicationPid()). |
| arg(agentPathCounter.fetchAndAddOrdered(1))); |
| |
| m_agent = new AgentAdaptor(this); |
| |
| if (!QDBusConnection::sessionBus().registerObject(m_agent_path, this)) |
| qCWarning(QT_BT_BLUEZ) << "Failed creating obex agent dbus objects"; |
| } |
| |
| QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection); |
| m_running = true; |
| } |
| |
| /*! |
| Destroys the QBluetoothTransferReply object. |
| */ |
| QBluetoothTransferReplyBluez::~QBluetoothTransferReplyBluez() |
| { |
| QDBusConnection::sessionBus().unregisterObject(m_agent_path); |
| delete m_client; |
| } |
| |
| bool QBluetoothTransferReplyBluez::start() |
| { |
| QFile *file = qobject_cast<QFile *>(m_source); |
| |
| if(!file){ |
| m_tempfile = new QTemporaryFile(this ); |
| m_tempfile->open(); |
| qCDebug(QT_BT_BLUEZ) << "Not a QFile, making a copy" << m_tempfile->fileName(); |
| if (!m_source->isReadable()) { |
| m_errorStr = QBluetoothTransferReply::tr("QIODevice cannot be read. " |
| "Make sure it is open for reading."); |
| m_error = QBluetoothTransferReply::IODeviceNotReadableError; |
| m_finished = true; |
| m_running = false; |
| |
| emit QBluetoothTransferReply::error(m_error); |
| emit finished(this); |
| return false; |
| } |
| |
| QFutureWatcher<bool> *watcher = new QFutureWatcher<bool>(); |
| QObject::connect(watcher, SIGNAL(finished()), this, SLOT(copyDone())); |
| |
| QFuture<bool> results = QtConcurrent::run(QBluetoothTransferReplyBluez::copyToTempFile, m_tempfile, m_source); |
| watcher->setFuture(results); |
| } |
| else { |
| if (!file->exists()) { |
| m_errorStr = QBluetoothTransferReply::tr("Source file does not exist"); |
| m_error = QBluetoothTransferReply::FileNotFoundError; |
| m_finished = true; |
| m_running = false; |
| |
| emit QBluetoothTransferReply::error(m_error); |
| emit finished(this); |
| return false; |
| } |
| if (request().address().isNull()) { |
| m_errorStr = QBluetoothTransferReply::tr("Invalid target address"); |
| m_error = QBluetoothTransferReply::HostNotFoundError; |
| m_finished = true; |
| m_running = false; |
| |
| emit QBluetoothTransferReply::error(m_error); |
| emit finished(this); |
| return false; |
| } |
| m_size = file->size(); |
| startOPP(file->fileName()); |
| } |
| return true; |
| } |
| |
| bool QBluetoothTransferReplyBluez::copyToTempFile(QIODevice *to, QIODevice *from) |
| { |
| QVector<char> block(4096); |
| int size; |
| |
| while ((size = from->read(block.data(), block.size())) > 0) { |
| if (size != to->write(block.data(), size)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void QBluetoothTransferReplyBluez::cleanupSession() |
| { |
| if (!m_objectPushBluez) |
| return; |
| |
| QDBusPendingReply<> reply = m_clientBluez->RemoveSession(QDBusObjectPath(m_objectPushBluez->path())); |
| reply.waitForFinished(); |
| if (reply.isError()) |
| qCWarning(QT_BT_BLUEZ) << "Abort: Cannot remove obex session"; |
| |
| delete m_objectPushBluez; |
| m_objectPushBluez = nullptr; |
| } |
| |
| void QBluetoothTransferReplyBluez::copyDone() |
| { |
| m_size = m_tempfile->size(); |
| startOPP(m_tempfile->fileName()); |
| QObject::sender()->deleteLater(); |
| } |
| |
| void QBluetoothTransferReplyBluez::sessionCreated(QDBusPendingCallWatcher *watcher) |
| { |
| QDBusPendingReply<QDBusObjectPath> reply = *watcher; |
| if (reply.isError()) { |
| qCWarning(QT_BT_BLUEZ) << "Failed to create obex session:" |
| << reply.error().name() << reply.reply().errorMessage(); |
| |
| m_errorStr = QBluetoothTransferReply::tr("Invalid target address"); |
| m_error = QBluetoothTransferReply::HostNotFoundError; |
| m_finished = true; |
| m_running = false; |
| |
| emit QBluetoothTransferReply::error(m_error); |
| emit finished(this); |
| |
| watcher->deleteLater(); |
| return; |
| } |
| |
| m_objectPushBluez = new OrgBluezObexObjectPush1Interface(QStringLiteral("org.bluez.obex"), |
| reply.value().path(), |
| QDBusConnection::sessionBus(), this); |
| QDBusPendingReply<QDBusObjectPath, QVariantMap> newReply = m_objectPushBluez->SendFile(fileToTranser); |
| QDBusPendingCallWatcher *newWatcher = new QDBusPendingCallWatcher(newReply, this); |
| connect(newWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), |
| SLOT(sessionStarted(QDBusPendingCallWatcher*))); |
| watcher->deleteLater(); |
| } |
| |
| void QBluetoothTransferReplyBluez::sessionStarted(QDBusPendingCallWatcher *watcher) |
| { |
| QDBusPendingReply<QDBusObjectPath, QVariantMap> reply = *watcher; |
| if (reply.isError()) { |
| qCWarning(QT_BT_BLUEZ) << "Failed to start obex session:" |
| << reply.error().name() << reply.reply().errorMessage(); |
| |
| m_errorStr = QBluetoothTransferReply::tr("Push session cannot be started"); |
| m_error = QBluetoothTransferReply::SessionError; |
| m_finished = true; |
| m_running = false; |
| |
| cleanupSession(); |
| |
| emit QBluetoothTransferReply::error(m_error); |
| emit finished(this); |
| |
| watcher->deleteLater(); |
| return; |
| } |
| |
| const QDBusObjectPath path = reply.argumentAt<0>(); |
| const QVariantMap map = reply.argumentAt<1>(); |
| m_transfer_path = path.path(); |
| |
| //watch the transfer |
| OrgFreedesktopDBusPropertiesInterface *properties = new OrgFreedesktopDBusPropertiesInterface( |
| QStringLiteral("org.bluez.obex"), path.path(), |
| QDBusConnection::sessionBus(), this); |
| connect(properties, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, |
| this, &QBluetoothTransferReplyBluez::sessionChanged); |
| |
| watcher->deleteLater(); |
| } |
| |
| void QBluetoothTransferReplyBluez::sessionChanged(const QString &interface, |
| const QVariantMap &changed_properties, |
| const QStringList &, const QDBusMessage &) |
| { |
| if (changed_properties.contains(QStringLiteral("Transferred"))) { |
| emit transferProgress( |
| changed_properties.value(QStringLiteral("Transferred")).toULongLong(), |
| m_size); |
| } |
| |
| if (changed_properties.contains(QStringLiteral("Status"))) { |
| const QString s = changed_properties. |
| value(QStringLiteral("Status")).toString(); |
| if (s == QStringLiteral("complete") |
| || s == QStringLiteral("error")) { |
| |
| m_transfer_path.clear(); |
| m_finished = true; |
| m_running = false; |
| |
| if (s == QStringLiteral("error")) { |
| m_error = QBluetoothTransferReply::UnknownError; |
| m_errorStr = tr("Unknown Error"); |
| |
| emit QBluetoothTransferReply::error(m_error); |
| } else { // complete |
| // allow progress bar to complete |
| emit transferProgress(m_size, m_size); |
| } |
| |
| cleanupSession(); |
| |
| emit finished(this); |
| } // ignore "active", "queued" & "suspended" status |
| } |
| qCDebug(QT_BT_BLUEZ) << "Transfer update:" << interface << changed_properties; |
| } |
| |
| void QBluetoothTransferReplyBluez::startOPP(const QString &filename) |
| { |
| if (m_client) { // Bluez 4 |
| QVariantMap device; |
| QStringList files; |
| |
| device.insert(QStringLiteral("Destination"), request().address().toString()); |
| files << filename; |
| |
| QDBusObjectPath path(m_agent_path); |
| QDBusPendingReply<> sendReply = m_client->SendFiles(device, files, path); |
| |
| QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(sendReply, this); |
| connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), |
| this, SLOT(sendReturned(QDBusPendingCallWatcher*))); |
| } else { //Bluez 5 |
| fileToTranser = filename; |
| QVariantMap mapping; |
| mapping.insert(QStringLiteral("Target"), QStringLiteral("opp")); |
| |
| QDBusPendingReply<QDBusObjectPath> reply = m_clientBluez->CreateSession( |
| request().address().toString(), mapping); |
| |
| QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); |
| connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), |
| SLOT(sessionCreated(QDBusPendingCallWatcher*))); |
| } |
| } |
| |
| void QBluetoothTransferReplyBluez::sendReturned(QDBusPendingCallWatcher *watcher) |
| { |
| QDBusPendingReply<> sendReply = *watcher; |
| if(sendReply.isError()){ |
| m_finished = true; |
| m_running = false; |
| m_errorStr = sendReply.error().message(); |
| if (m_errorStr == QStringLiteral("Could not open file for sending")) { |
| m_error = QBluetoothTransferReply::FileNotFoundError; |
| m_errorStr = tr("Could not open file for sending"); |
| } else if (m_errorStr == QStringLiteral("The transfer was canceled")) { |
| m_error = QBluetoothTransferReply::UserCanceledTransferError; |
| m_errorStr = tr("The transfer was canceled"); |
| } else { |
| m_error = QBluetoothTransferReply::UnknownError; |
| } |
| |
| emit QBluetoothTransferReply::error(m_error); |
| emit finished(this); |
| } |
| } |
| |
| QBluetoothTransferReply::TransferError QBluetoothTransferReplyBluez::error() const |
| { |
| return m_error; |
| } |
| |
| QString QBluetoothTransferReplyBluez::errorString() const |
| { |
| return m_errorStr; |
| } |
| |
| void QBluetoothTransferReplyBluez::Complete(const QDBusObjectPath &in0) |
| { |
| Q_UNUSED(in0); |
| m_transfer_path.clear(); |
| m_finished = true; |
| m_running = false; |
| } |
| |
| void QBluetoothTransferReplyBluez::Error(const QDBusObjectPath &in0, const QString &in1) |
| { |
| Q_UNUSED(in0); |
| m_transfer_path.clear(); |
| m_finished = true; |
| m_running = false; |
| m_errorStr = in1; |
| if (in1 == QStringLiteral("Could not open file for sending")) { |
| m_error = QBluetoothTransferReply::FileNotFoundError; |
| m_errorStr = tr("Could not open file for sending"); |
| } else if (in1 == QStringLiteral("Operation canceled")) { |
| m_error = QBluetoothTransferReply::UserCanceledTransferError; |
| m_errorStr = QBluetoothTransferReply::tr("Operation canceled"); |
| } else { |
| m_error = QBluetoothTransferReply::UnknownError; |
| } |
| |
| emit QBluetoothTransferReply::error(m_error); |
| emit finished(this); |
| } |
| |
| void QBluetoothTransferReplyBluez::Progress(const QDBusObjectPath &in0, qulonglong in1) |
| { |
| Q_UNUSED(in0); |
| emit transferProgress(in1, m_size); |
| } |
| |
| void QBluetoothTransferReplyBluez::Release() |
| { |
| if(m_errorStr.isEmpty()) |
| emit finished(this); |
| } |
| |
| QString QBluetoothTransferReplyBluez::Request(const QDBusObjectPath &in0) |
| { |
| m_transfer_path = in0.path(); |
| |
| return QString(); |
| } |
| |
| /*! |
| Returns true if this reply has finished; otherwise returns false. |
| */ |
| bool QBluetoothTransferReplyBluez::isFinished() const |
| { |
| return m_finished; |
| } |
| |
| /*! |
| Returns true if this reply is running; otherwise returns false. |
| */ |
| bool QBluetoothTransferReplyBluez::isRunning() const |
| { |
| return m_running; |
| } |
| |
| void QBluetoothTransferReplyBluez::abort() |
| { |
| if (m_transfer_path.isEmpty()) |
| return; |
| |
| if (m_client) { |
| OrgOpenobexTransferInterface xfer(QStringLiteral("org.openobex.client"), |
| m_transfer_path, |
| QDBusConnection::sessionBus()); |
| |
| QDBusPendingReply<> reply = xfer.Cancel(); |
| reply.waitForFinished(); |
| if (reply.isError()) |
| qCWarning(QT_BT_BLUEZ) << "Failed to abort transfer" << reply.error().message(); |
| |
| } else if (m_clientBluez) { |
| OrgBluezObexTransfer1Interface iface(QStringLiteral("org.bluez.obex"), |
| m_transfer_path, |
| QDBusConnection::sessionBus()); |
| |
| QDBusPendingReply<> reply = iface.Cancel(); |
| reply.waitForFinished(); |
| if (reply.isError()) |
| qCDebug(QT_BT_BLUEZ) << "Failed to abort transfer" << reply.error().message(); |
| |
| m_error = QBluetoothTransferReply::UserCanceledTransferError; |
| m_errorStr = tr("Operation canceled"); |
| |
| cleanupSession(); |
| |
| emit QBluetoothTransferReply::error(m_error); |
| emit finished(this); |
| } |
| } |
| |
| #include "moc_qbluetoothtransferreply_bluez_p.cpp" |
| |
| QT_END_NAMESPACE |