1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtBluetooth module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40
41#include "qbluetoothtransferreply_bluez_p.h"
42#include "qbluetoothaddress.h"
43
44#include "bluez/obex_client_p.h"
45#include "bluez/obex_agent_p.h"
46#include "bluez/obex_transfer_p.h"
47#include "bluez/bluez5_helper_p.h"
48#include "bluez/obex_client1_bluez5_p.h"
49#include "bluez/obex_objectpush1_bluez5_p.h"
50#include "bluez/obex_transfer1_bluez5_p.h"
51#include "bluez/properties_p.h"
52#include "qbluetoothtransferreply.h"
53
54#include <QtCore/QAtomicInt>
55#include <QtCore/QLoggingCategory>
56#include <QtCore/QVector>
57#include <QFuture>
58#include <QFutureWatcher>
59#include <QtConcurrentRun>
60
61static const QLatin1String agentPath("/qt/agent");
62static QAtomicInt agentPathCounter;
63
64QT_BEGIN_NAMESPACE
65
66Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
67
68QBluetoothTransferReplyBluez::QBluetoothTransferReplyBluez(QIODevice *input, const QBluetoothTransferRequest &request,
69 QBluetoothTransferManager *parent)
70: QBluetoothTransferReply(parent),
71 m_source(input),
72 m_running(false), m_finished(false), m_size(0),
73 m_error(QBluetoothTransferReply::NoError), m_errorStr(), m_transfer_path()
74{
75 setRequest(request);
76 setManager(parent);
77
78 if (!input) {
79 qCWarning(QT_BT_BLUEZ) << "Invalid input device (null)";
80 m_errorStr = QBluetoothTransferReply::tr("Invalid input device (null)");
81 m_error = QBluetoothTransferReply::FileNotFoundError;
82 m_finished = true;
83 return;
84 }
85
86 if (isBluez5()) {
87 m_clientBluez = new OrgBluezObexClient1Interface(QStringLiteral("org.bluez.obex"),
88 QStringLiteral("/org/bluez/obex"),
89 QDBusConnection::sessionBus(), this);
90
91
92 } else {
93 m_client = new OrgOpenobexClientInterface(QStringLiteral("org.openobex.client"),
94 QStringLiteral("/"),
95 QDBusConnection::sessionBus());
96
97 m_agent_path = agentPath;
98 m_agent_path.append(QStringLiteral("/%1%2/%3").
99 arg(sanitizeNameForDBus(QCoreApplication::applicationName())).
100 arg(QCoreApplication::applicationPid()).
101 arg(agentPathCounter.fetchAndAddOrdered(1)));
102
103 m_agent = new AgentAdaptor(this);
104
105 if (!QDBusConnection::sessionBus().registerObject(m_agent_path, this))
106 qCWarning(QT_BT_BLUEZ) << "Failed creating obex agent dbus objects";
107 }
108
109 QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection);
110 m_running = true;
111}
112
113/*!
114 Destroys the QBluetoothTransferReply object.
115*/
116QBluetoothTransferReplyBluez::~QBluetoothTransferReplyBluez()
117{
118 QDBusConnection::sessionBus().unregisterObject(m_agent_path);
119 delete m_client;
120}
121
122bool QBluetoothTransferReplyBluez::start()
123{
124 QFile *file = qobject_cast<QFile *>(m_source);
125
126 if(!file){
127 m_tempfile = new QTemporaryFile(this );
128 m_tempfile->open();
129 qCDebug(QT_BT_BLUEZ) << "Not a QFile, making a copy" << m_tempfile->fileName();
130 if (!m_source->isReadable()) {
131 m_errorStr = QBluetoothTransferReply::tr("QIODevice cannot be read. "
132 "Make sure it is open for reading.");
133 m_error = QBluetoothTransferReply::IODeviceNotReadableError;
134 m_finished = true;
135 m_running = false;
136
137 emit QBluetoothTransferReply::error(m_error);
138 emit finished(this);
139 return false;
140 }
141
142 QFutureWatcher<bool> *watcher = new QFutureWatcher<bool>();
143 QObject::connect(watcher, SIGNAL(finished()), this, SLOT(copyDone()));
144
145 QFuture<bool> results = QtConcurrent::run(QBluetoothTransferReplyBluez::copyToTempFile, m_tempfile, m_source);
146 watcher->setFuture(results);
147 }
148 else {
149 if (!file->exists()) {
150 m_errorStr = QBluetoothTransferReply::tr("Source file does not exist");
151 m_error = QBluetoothTransferReply::FileNotFoundError;
152 m_finished = true;
153 m_running = false;
154
155 emit QBluetoothTransferReply::error(m_error);
156 emit finished(this);
157 return false;
158 }
159 if (request().address().isNull()) {
160 m_errorStr = QBluetoothTransferReply::tr("Invalid target address");
161 m_error = QBluetoothTransferReply::HostNotFoundError;
162 m_finished = true;
163 m_running = false;
164
165 emit QBluetoothTransferReply::error(m_error);
166 emit finished(this);
167 return false;
168 }
169 m_size = file->size();
170 startOPP(file->fileName());
171 }
172 return true;
173}
174
175bool QBluetoothTransferReplyBluez::copyToTempFile(QIODevice *to, QIODevice *from)
176{
177 QVector<char> block(4096);
178 int size;
179
180 while ((size = from->read(block.data(), block.size())) > 0) {
181 if (size != to->write(block.data(), size)) {
182 return false;
183 }
184 }
185
186 return true;
187}
188
189void QBluetoothTransferReplyBluez::cleanupSession()
190{
191 if (!m_objectPushBluez)
192 return;
193
194 QDBusPendingReply<> reply = m_clientBluez->RemoveSession(QDBusObjectPath(m_objectPushBluez->path()));
195 reply.waitForFinished();
196 if (reply.isError())
197 qCWarning(QT_BT_BLUEZ) << "Abort: Cannot remove obex session";
198
199 delete m_objectPushBluez;
200 m_objectPushBluez = nullptr;
201}
202
203void QBluetoothTransferReplyBluez::copyDone()
204{
205 m_size = m_tempfile->size();
206 startOPP(m_tempfile->fileName());
207 QObject::sender()->deleteLater();
208}
209
210void QBluetoothTransferReplyBluez::sessionCreated(QDBusPendingCallWatcher *watcher)
211{
212 QDBusPendingReply<QDBusObjectPath> reply = *watcher;
213 if (reply.isError()) {
214 qCWarning(QT_BT_BLUEZ) << "Failed to create obex session:"
215 << reply.error().name() << reply.reply().errorMessage();
216
217 m_errorStr = QBluetoothTransferReply::tr("Invalid target address");
218 m_error = QBluetoothTransferReply::HostNotFoundError;
219 m_finished = true;
220 m_running = false;
221
222 emit QBluetoothTransferReply::error(m_error);
223 emit finished(this);
224
225 watcher->deleteLater();
226 return;
227 }
228
229 m_objectPushBluez = new OrgBluezObexObjectPush1Interface(QStringLiteral("org.bluez.obex"),
230 reply.value().path(),
231 QDBusConnection::sessionBus(), this);
232 QDBusPendingReply<QDBusObjectPath, QVariantMap> newReply = m_objectPushBluez->SendFile(fileToTranser);
233 QDBusPendingCallWatcher *newWatcher = new QDBusPendingCallWatcher(newReply, this);
234 connect(newWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
235 SLOT(sessionStarted(QDBusPendingCallWatcher*)));
236 watcher->deleteLater();
237}
238
239void QBluetoothTransferReplyBluez::sessionStarted(QDBusPendingCallWatcher *watcher)
240{
241 QDBusPendingReply<QDBusObjectPath, QVariantMap> reply = *watcher;
242 if (reply.isError()) {
243 qCWarning(QT_BT_BLUEZ) << "Failed to start obex session:"
244 << reply.error().name() << reply.reply().errorMessage();
245
246 m_errorStr = QBluetoothTransferReply::tr("Push session cannot be started");
247 m_error = QBluetoothTransferReply::SessionError;
248 m_finished = true;
249 m_running = false;
250
251 cleanupSession();
252
253 emit QBluetoothTransferReply::error(m_error);
254 emit finished(this);
255
256 watcher->deleteLater();
257 return;
258 }
259
260 const QDBusObjectPath path = reply.argumentAt<0>();
261 const QVariantMap map = reply.argumentAt<1>();
262 m_transfer_path = path.path();
263
264 //watch the transfer
265 OrgFreedesktopDBusPropertiesInterface *properties = new OrgFreedesktopDBusPropertiesInterface(
266 QStringLiteral("org.bluez.obex"), path.path(),
267 QDBusConnection::sessionBus(), this);
268 connect(properties, SIGNAL(PropertiesChanged(QString,QVariantMap,QStringList)),
269 SLOT(sessionChanged(QString,QVariantMap,QStringList)));
270
271 watcher->deleteLater();
272}
273
274void QBluetoothTransferReplyBluez::sessionChanged(const QString &interface,
275 const QVariantMap &changed_properties,
276 const QStringList &)
277{
278 if (changed_properties.contains(QStringLiteral("Transferred"))) {
279 emit transferProgress(
280 changed_properties.value(QStringLiteral("Transferred")).toULongLong(),
281 m_size);
282 }
283
284 if (changed_properties.contains(QStringLiteral("Status"))) {
285 const QString s = changed_properties.
286 value(QStringLiteral("Status")).toString();
287 if (s == QStringLiteral("complete")
288 || s == QStringLiteral("error")) {
289
290 m_transfer_path.clear();
291 m_finished = true;
292 m_running = false;
293
294 if (s == QStringLiteral("error")) {
295 m_error = QBluetoothTransferReply::UnknownError;
296 m_errorStr = tr("Unknown Error");
297
298 emit QBluetoothTransferReply::error(m_error);
299 } else { // complete
300 // allow progress bar to complete
301 emit transferProgress(m_size, m_size);
302 }
303
304 cleanupSession();
305
306 emit finished(this);
307 } // ignore "active", "queued" & "suspended" status
308 }
309 qCDebug(QT_BT_BLUEZ) << "Transfer update:" << interface << changed_properties;
310}
311
312void QBluetoothTransferReplyBluez::startOPP(const QString &filename)
313{
314 if (m_client) { // Bluez 4
315 QVariantMap device;
316 QStringList files;
317
318 device.insert(QStringLiteral("Destination"), request().address().toString());
319 files << filename;
320
321 QDBusObjectPath path(m_agent_path);
322 QDBusPendingReply<> sendReply = m_client->SendFiles(device, files, path);
323
324 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(sendReply, this);
325 connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
326 this, SLOT(sendReturned(QDBusPendingCallWatcher*)));
327 } else { //Bluez 5
328 fileToTranser = filename;
329 QVariantMap mapping;
330 mapping.insert(QStringLiteral("Target"), QStringLiteral("opp"));
331
332 QDBusPendingReply<QDBusObjectPath> reply = m_clientBluez->CreateSession(
333 request().address().toString(), mapping);
334
335 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
336 connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
337 SLOT(sessionCreated(QDBusPendingCallWatcher*)));
338 }
339}
340
341void QBluetoothTransferReplyBluez::sendReturned(QDBusPendingCallWatcher *watcher)
342{
343 QDBusPendingReply<> sendReply = *watcher;
344 if(sendReply.isError()){
345 m_finished = true;
346 m_running = false;
347 m_errorStr = sendReply.error().message();
348 if (m_errorStr == QStringLiteral("Could not open file for sending")) {
349 m_error = QBluetoothTransferReply::FileNotFoundError;
350 m_errorStr = tr("Could not open file for sending");
351 } else if (m_errorStr == QStringLiteral("The transfer was canceled")) {
352 m_error = QBluetoothTransferReply::UserCanceledTransferError;
353 m_errorStr = tr("The transfer was canceled");
354 } else {
355 m_error = QBluetoothTransferReply::UnknownError;
356 }
357
358 emit QBluetoothTransferReply::error(m_error);
359 emit finished(this);
360 }
361}
362
363QBluetoothTransferReply::TransferError QBluetoothTransferReplyBluez::error() const
364{
365 return m_error;
366}
367
368QString QBluetoothTransferReplyBluez::errorString() const
369{
370 return m_errorStr;
371}
372
373void QBluetoothTransferReplyBluez::Complete(const QDBusObjectPath &in0)
374{
375 Q_UNUSED(in0);
376 m_transfer_path.clear();
377 m_finished = true;
378 m_running = false;
379}
380
381void QBluetoothTransferReplyBluez::Error(const QDBusObjectPath &in0, const QString &in1)
382{
383 Q_UNUSED(in0);
384 m_transfer_path.clear();
385 m_finished = true;
386 m_running = false;
387 m_errorStr = in1;
388 if (in1 == QStringLiteral("Could not open file for sending")) {
389 m_error = QBluetoothTransferReply::FileNotFoundError;
390 m_errorStr = tr("Could not open file for sending");
391 } else if (in1 == QStringLiteral("Operation canceled")) {
392 m_error = QBluetoothTransferReply::UserCanceledTransferError;
393 m_errorStr = QBluetoothTransferReply::tr("Operation canceled");
394 } else {
395 m_error = QBluetoothTransferReply::UnknownError;
396 }
397
398 emit QBluetoothTransferReply::error(m_error);
399 emit finished(this);
400}
401
402void QBluetoothTransferReplyBluez::Progress(const QDBusObjectPath &in0, qulonglong in1)
403{
404 Q_UNUSED(in0);
405 emit transferProgress(in1, m_size);
406}
407
408void QBluetoothTransferReplyBluez::Release()
409{
410 if(m_errorStr.isEmpty())
411 emit finished(this);
412}
413
414QString QBluetoothTransferReplyBluez::Request(const QDBusObjectPath &in0)
415{
416 m_transfer_path = in0.path();
417
418 return QString();
419}
420
421/*!
422 Returns true if this reply has finished; otherwise returns false.
423*/
424bool QBluetoothTransferReplyBluez::isFinished() const
425{
426 return m_finished;
427}
428
429/*!
430 Returns true if this reply is running; otherwise returns false.
431*/
432bool QBluetoothTransferReplyBluez::isRunning() const
433{
434 return m_running;
435}
436
437void QBluetoothTransferReplyBluez::abort()
438{
439 if (m_transfer_path.isEmpty())
440 return;
441
442 if (m_client) {
443 OrgOpenobexTransferInterface xfer(QStringLiteral("org.openobex.client"),
444 m_transfer_path,
445 QDBusConnection::sessionBus());
446
447 QDBusPendingReply<> reply = xfer.Cancel();
448 reply.waitForFinished();
449 if (reply.isError())
450 qCWarning(QT_BT_BLUEZ) << "Failed to abort transfer" << reply.error().message();
451
452 } else if (m_clientBluez) {
453 OrgBluezObexTransfer1Interface iface(QStringLiteral("org.bluez.obex"),
454 m_transfer_path,
455 QDBusConnection::sessionBus());
456
457 QDBusPendingReply<> reply = iface.Cancel();
458 reply.waitForFinished();
459 if (reply.isError())
460 qCDebug(QT_BT_BLUEZ) << "Failed to abort transfer" << reply.error().message();
461
462 m_error = QBluetoothTransferReply::UserCanceledTransferError;
463 m_errorStr = tr("Operation canceled");
464
465 cleanupSession();
466
467 emit QBluetoothTransferReply::error(m_error);
468 emit finished(this);
469 }
470}
471
472#include "moc_qbluetoothtransferreply_bluez_p.cpp"
473
474QT_END_NAMESPACE
475