Warning: That file was not part of the compilation database. It may have many parsing errors.

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#include "qbluetoothservicediscoveryagent.h"
41#include "qbluetoothtransferreply_osx_p.h"
42#include "osx/osxbtobexsession_p.h"
43#include "qbluetoothserviceinfo.h"
44#include "osx/osxbtutility_p.h"
45#include "osx/uistrings_p.h"
46#include "qbluetoothuuid.h"
47
48
49#include <QtCore/qcoreapplication.h>
50#include <QtCore/qloggingcategory.h>
51#include <QtCore/qtemporaryfile.h>
52#include <QtCore/qmetaobject.h>
53#include <QtCore/qfileinfo.h>
54#include <QtCore/qstring.h>
55#include <QtCore/qdebug.h>
56#include <QtCore/qfile.h>
57#include <QtCore/qdir.h>
58
59QT_BEGIN_NAMESPACE
60
61class QBluetoothTransferReplyOSXPrivate : OSXBluetooth::OBEXSessionDelegate
62{
63 friend class QBluetoothTransferReplyOSX;
64public:
65 QBluetoothTransferReplyOSXPrivate(QBluetoothTransferReplyOSX *q, QIODevice *inputStream);
66
67 ~QBluetoothTransferReplyOSXPrivate();
68
69 bool isActive() const;
70 bool startOPP(const QBluetoothAddress &device);
71
72 //
73 void sendConnect(const QBluetoothAddress &device, quint16 channelID);
74 void sendPut();
75
76private:
77 // OBEX session delegate:
78 void OBEXConnectError(OBEXError errorCode, OBEXOpCode response) override;
79 void OBEXConnectSuccess() override;
80
81 void OBEXAbortSuccess() override;
82
83 void OBEXPutDataSent(quint32 current, quint32 total) override;
84 void OBEXPutSuccess() override;
85 void OBEXPutError(OBEXError error, OBEXOpCode response) override;
86
87 QBluetoothTransferReplyOSX *q_ptr;
88
89 QIODevice *inputStream;
90
91 QBluetoothTransferReply::TransferError error;
92 QString errorString;
93
94 // Set requestComplete, error, description, emit error, emit finished.
95 // Too many things in one, but not to repeat this code everywhere.
96 void setReplyError(QBluetoothTransferReply::TransferError errorCode,
97 const QString &errorMessage);
98
99 // With a given API, we have to discover a service first
100 // since we need a channel ID to work with OBEX session.
101 // Also, service discovery agent does not have an interface
102 // to test discovery mode, that's why we have this bool here.
103 bool minimalScan;
104 QScopedPointer<QBluetoothServiceDiscoveryAgent> agent;
105
106 // The next step is to create an OBEX session:
107 typedef OSXBluetooth::ObjCScopedPointer<ObjCOBEXSession> OBEXSession;
108 OBEXSession session;
109
110 // Both success and failure to send - transfer is complete.
111 bool requestComplete;
112
113 // We need a temporary file to generate an unique name
114 // in case inputStream is not a file. QTemporaryFile not
115 // only creates a random name, it also guarantees this name
116 // is unique. The amount of code to generate such a name
117 // is amaizingly huge (and will require global variables)
118 // - so a temporary file can help.
119 QScopedPointer<QTemporaryFile> temporaryFile;
120};
121
122QBluetoothTransferReplyOSXPrivate::QBluetoothTransferReplyOSXPrivate(QBluetoothTransferReplyOSX *q,
123 QIODevice *input)
124 : q_ptr(q),
125 inputStream(input),
126 error(QBluetoothTransferReply::NoError),
127 minimalScan(true),
128 requestComplete(false)
129{
130 Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)");
131}
132
133QBluetoothTransferReplyOSXPrivate::~QBluetoothTransferReplyOSXPrivate()
134{
135 // closeSession will set a delegate to null.
136 // The OBEX session will be closed then. If
137 // somehow IOBluetooth/OBEX still has a reference to our
138 // session, it will not call any of delegate's callbacks.
139 if (session)
140 [session closeSession];
141}
142
143bool QBluetoothTransferReplyOSXPrivate::isActive() const
144{
145 return agent || (session && [session hasActiveRequest]);
146}
147
148bool QBluetoothTransferReplyOSXPrivate::startOPP(const QBluetoothAddress &device)
149{
150 Q_ASSERT_X(!isActive(), Q_FUNC_INFO, "already started");
151 Q_ASSERT_X(!device.isNull(), Q_FUNC_INFO, "invalid device address");
152
153 errorString.clear();
154 error = QBluetoothTransferReply::NoError;
155
156 agent.reset(new QBluetoothServiceDiscoveryAgent);
157
158 agent->setRemoteAddress(device);
159 agent->setUuidFilter(QBluetoothUuid(QBluetoothUuid::ObexObjectPush));
160
161 QObject::connect(agent.data(), SIGNAL(finished()), q_ptr, SLOT(serviceDiscoveryFinished()));
162 QObject::connect(agent.data(), SIGNAL(error(QBluetoothServiceDiscoveryAgent::Error)),
163 q_ptr, SLOT(serviceDiscoveryError(QBluetoothServiceDiscoveryAgent::Error)));
164
165 minimalScan = true;
166 agent->start(QBluetoothServiceDiscoveryAgent::MinimalDiscovery);
167
168 // We probably failed already.
169 return error == QBluetoothTransferReply::NoError;
170}
171
172void QBluetoothTransferReplyOSXPrivate::sendConnect(const QBluetoothAddress &device, quint16 channelID)
173{
174 Q_ASSERT_X(!session, Q_FUNC_INFO, "session is already active");
175
176 error = QBluetoothTransferReply::NoError;
177 errorString.clear();
178
179 if (device.isNull() || !channelID) {
180 qCWarning(QT_BT_OSX) << "invalid device address or port";
181 setReplyError(QBluetoothTransferReply::HostNotFoundError,
182 QCoreApplication::translate(TRANSFER_REPLY, TR_INVAL_TARGET));
183 return;
184 }
185
186 OBEXSession newSession([[ObjCOBEXSession alloc] initWithDelegate:this
187 remoteDevice:device channelID:channelID]);
188 if (!newSession) {
189 qCWarning(QT_BT_OSX) << "failed to allocate OSXBTOBEXSession object";
190
191 setReplyError(QBluetoothTransferReply::UnknownError,
192 QCoreApplication::translate(TRANSFER_REPLY, TR_SESSION_NO_START));
193 return;
194 }
195
196 const OBEXError status = [newSession OBEXConnect];
197
198 if ((status == kOBEXSuccess || status == kOBEXSessionAlreadyConnectedError)
199 && error == QBluetoothTransferReply::NoError) {
200 session.reset(newSession.take());
201 if ([session isConnected])
202 sendPut();// Connected, send a PUT request.
203 } else {
204 qCWarning(QT_BT_OSX) << "OBEXConnect failed";
205
206 if (error == QBluetoothTransferReply::NoError) {
207 // The error is not set yet.
208 error = QBluetoothTransferReply::SessionError;
209 errorString = QCoreApplication::translate(TRANSFER_REPLY, TR_CONNECT_FAILED);
210 }
211
212 requestComplete = true;
213 emit q_ptr->error(error);
214 emit q_ptr->finished(q_ptr);
215 }
216}
217
218void QBluetoothTransferReplyOSXPrivate::sendPut()
219{
220 Q_ASSERT_X(inputStream, Q_FUNC_INFO, "invalid input stream (null)");
221 Q_ASSERT_X(session, Q_FUNC_INFO, "invalid OBEX session (nil)");
222 Q_ASSERT_X([session isConnected], Q_FUNC_INFO, "not connected");
223 Q_ASSERT_X(![session hasActiveRequest], Q_FUNC_INFO,
224 "session already has an active request");
225
226 QString fileName;
227 QFile *const file = qobject_cast<QFile *>(inputStream);
228 if (file) {
229 if (!file->exists()) {
230 setReplyError(QBluetoothTransferReply::FileNotFoundError,
231 QCoreApplication::translate(TRANSFER_REPLY, TR_FILE_NOT_EXIST));
232 return;
233 } else if (!file->isReadable()) {
234 file->open(QIODevice::ReadOnly);
235 if (!file->isReadable()) {
236 setReplyError(QBluetoothTransferReply::IODeviceNotReadableError,
237 QCoreApplication::translate(TRANSFER_REPLY, TR_NOT_READ_IODEVICE));
238 return;
239 }
240 }
241
242 fileName = file->fileName();
243 } else {
244 if (!inputStream->isReadable()) {
245 setReplyError(QBluetoothTransferReply::IODeviceNotReadableError,
246 QCoreApplication::translate(TRANSFER_REPLY, TR_NOT_READ_IODEVICE));
247 return;
248 }
249
250 temporaryFile.reset(new QTemporaryFile);
251 temporaryFile->open();
252 fileName = temporaryFile->fileName();
253 }
254
255 const QFileInfo fileInfo(fileName);
256 fileName = fileInfo.fileName();
257
258 if ([session OBEXPutFile:inputStream withName:fileName] != kOBEXSuccess) {
259 // TODO: convert OBEXError into something reasonable?
260 setReplyError(QBluetoothTransferReply::SessionError,
261 QCoreApplication::translate(TRANSFER_REPLY, TR_SESSION_FAILED));
262 }
263}
264
265
266void QBluetoothTransferReplyOSXPrivate::OBEXConnectError(OBEXError errorCode, OBEXOpCode response)
267{
268 Q_UNUSED(errorCode)
269 Q_UNUSED(response)
270
271 if (session) {
272 setReplyError(QBluetoothTransferReply::SessionError,
273 QCoreApplication::translate(TRANSFER_REPLY, TR_CONNECT_FAILED));
274 } else {
275 // Else we're still in OBEXConnect, in a call-back
276 // and do not want to emit yet (will be done a bit later).
277 error = QBluetoothTransferReply::SessionError;
278 errorString = QCoreApplication::translate(TRANSFER_REPLY, TR_CONNECT_FAILED);
279 requestComplete = true;
280 }
281}
282
283void QBluetoothTransferReplyOSXPrivate::OBEXConnectSuccess()
284{
285 // Now that OBEX connect succeeded, we can send an OBEX put request.
286 if (!session) {
287 // We're still in OBEXConnect(), it'll take care of next steps.
288 return;
289 }
290
291 sendPut();
292}
293
294void QBluetoothTransferReplyOSXPrivate::OBEXAbortSuccess()
295{
296 // TODO:
297}
298
299void QBluetoothTransferReplyOSXPrivate::OBEXPutDataSent(quint32 current, quint32 total)
300{
301 emit q_ptr->transferProgress(current, total);
302}
303
304void QBluetoothTransferReplyOSXPrivate::OBEXPutSuccess()
305{
306 requestComplete = true;
307 emit q_ptr->finished(q_ptr);
308}
309
310void QBluetoothTransferReplyOSXPrivate::OBEXPutError(OBEXError errorCode, OBEXOpCode responseCode)
311{
312 // Error can be reported by errorCode or responseCode
313 // (that's how errors are reported in OBEXSession events).
314 // errorCode and responseCode are "mutually exclusive".
315
316 Q_UNUSED(responseCode)
317
318 if (errorCode != kOBEXSuccess) {
319 // TODO: errorCode -> TransferError.
320 } else {
321 // TODO: a response code can give some interesting information,
322 // like "forbidden" etc. - convert this into more reasonable error.
323 }
324
325 setReplyError(QBluetoothTransferReply::SessionError,
326 QCoreApplication::translate(TRANSFER_REPLY, TR_SESSION_FAILED));
327}
328
329void QBluetoothTransferReplyOSXPrivate::setReplyError(QBluetoothTransferReply::TransferError errorCode,
330 const QString &description)
331{
332 // Not to be used to clear an error!
333
334 error = errorCode;
335 errorString = description;
336 requestComplete = true;
337 emit q_ptr->error(error);
338 emit q_ptr->finished(q_ptr);
339}
340
341
342QBluetoothTransferReplyOSX::QBluetoothTransferReplyOSX(QIODevice *input,
343 const QBluetoothTransferRequest &request,
344 QBluetoothTransferManager *manager)
345 : QBluetoothTransferReply(manager)
346
347{
348 Q_UNUSED(input)
349
350 setManager(manager);
351 setRequest(request);
352
353 osx_d_ptr.reset(new QBluetoothTransferReplyOSXPrivate(this, input));
354
355 if (input) {
356 QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection);
357 } else {
358 qCWarning(QT_BT_OSX) << "invalid input stream (null)";
359 osx_d_ptr->requestComplete = true;
360 osx_d_ptr->errorString = QCoreApplication::translate(TRANSFER_REPLY, TR_INVALID_DEVICE);
361 osx_d_ptr->error = FileNotFoundError;
362 QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
363 Q_ARG(QBluetoothTransferReply::TransferError, FileNotFoundError));
364 }
365}
366
367QBluetoothTransferReplyOSX::~QBluetoothTransferReplyOSX()
368{
369 // A dtor to make a scoped pointer with incomplete type happy.
370}
371
372QBluetoothTransferReply::TransferError QBluetoothTransferReplyOSX::error() const
373{
374 return osx_d_ptr->error;
375}
376
377QString QBluetoothTransferReplyOSX::errorString() const
378{
379 return osx_d_ptr->errorString;
380}
381
382bool QBluetoothTransferReplyOSX::isFinished() const
383{
384 return osx_d_ptr->requestComplete;
385}
386
387bool QBluetoothTransferReplyOSX::isRunning() const
388{
389 return osx_d_ptr->isActive();
390}
391
392bool QBluetoothTransferReplyOSX::abort()
393{
394 // Reset a delegate.
395 [osx_d_ptr->session closeSession];
396 // Should never be called from an OBEX callback!
397 osx_d_ptr->session.reset(nullptr);
398
399 // Not setReplyError, we emit finished only!
400 osx_d_ptr->requestComplete = true;
401 osx_d_ptr->errorString = QCoreApplication::translate(TRANSFER_REPLY, TR_OP_CANCEL);
402 osx_d_ptr->error = UserCanceledTransferError;
403
404 emit finished(this);
405
406 return true;
407}
408
409bool QBluetoothTransferReplyOSX::start()
410{
411 // OBEXSession requires a channel ID and we have to find it first,
412 // using QBluetoothServiceDiscoveryAgent (singleDevice, OBEX uuid filter, start
413 // from MinimalDiscovery mode and continue with FullDiscovery if
414 // MinimalDiscovery fails.
415
416 if (!osx_d_ptr->isActive()) {
417 // Step 0: find a channelID.
418 if (request().address().isNull()) {
419 qCWarning(QT_BT_OSX) << "invalid device address";
420 osx_d_ptr->setReplyError(HostNotFoundError,
421 QCoreApplication::translate(TRANSFER_REPLY, TR_INVAL_TARGET));
422 return false;
423 }
424
425 return osx_d_ptr->startOPP(request().address());
426 } else {
427 osx_d_ptr->setReplyError(UnknownError,
428 QCoreApplication::translate(TRANSFER_REPLY, TR_IN_PROGRESS));
429 return false;
430 }
431}
432
433void QBluetoothTransferReplyOSX::serviceDiscoveryFinished()
434{
435 Q_ASSERT_X(osx_d_ptr->agent.data(), Q_FUNC_INFO,
436 "invalid service discovery agent (null)");
437
438 const QList<QBluetoothServiceInfo> services = osx_d_ptr->agent->discoveredServices();
439 if (services.size()) {
440 // TODO: what if we have several?
441 const QBluetoothServiceInfo &foundOBEX = services.front();
442 osx_d_ptr->sendConnect(request().address(), foundOBEX.serverChannel());
443 } else {
444 if (osx_d_ptr->minimalScan) {
445 // Try full discovery now.
446 osx_d_ptr->minimalScan = false;
447 osx_d_ptr->agent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
448 } else {
449 // No service record, no channel ID, no OBEX session.
450 osx_d_ptr->setReplyError(HostNotFoundError,
451 QCoreApplication::translate(TRANSFER_REPLY, TR_SERVICE_NO_FOUND));
452 }
453 }
454}
455
456void QBluetoothTransferReplyOSX::serviceDiscoveryError(QBluetoothServiceDiscoveryAgent::Error errorCode)
457{
458 Q_ASSERT_X(osx_d_ptr->agent.data(), Q_FUNC_INFO,
459 "invalid service discovery agent (null)");
460
461 if (errorCode == QBluetoothServiceDiscoveryAgent::PoweredOffError) {
462 // There's nothing else we can do.
463 osx_d_ptr->setReplyError(UnknownError,
464 QCoreApplication::translate(DEV_DISCOVERY, DD_POWERED_OFF));
465 return;
466 }
467
468 if (osx_d_ptr->minimalScan) {// Try again, this time in FullDiscovery mode.
469 osx_d_ptr->minimalScan = false;
470 osx_d_ptr->agent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
471 } else {
472 osx_d_ptr->setReplyError(HostNotFoundError,
473 QCoreApplication::translate(TRANSFER_REPLY, TR_INVAL_TARGET));
474 }
475}
476
477QT_END_NAMESPACE
478

Warning: That file was not part of the compilation database. It may have many parsing errors.