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 | |
59 | QT_BEGIN_NAMESPACE |
60 | |
61 | class QBluetoothTransferReplyOSXPrivate : OSXBluetooth::OBEXSessionDelegate |
62 | { |
63 | friend class QBluetoothTransferReplyOSX; |
64 | public: |
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 | |
76 | private: |
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 | |
122 | QBluetoothTransferReplyOSXPrivate::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 | |
133 | QBluetoothTransferReplyOSXPrivate::~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 | |
143 | bool QBluetoothTransferReplyOSXPrivate::isActive() const |
144 | { |
145 | return agent || (session && [session hasActiveRequest]); |
146 | } |
147 | |
148 | bool 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 | |
172 | void 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 | |
218 | void 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 | |
266 | void 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 | |
283 | void 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 | |
294 | void QBluetoothTransferReplyOSXPrivate::OBEXAbortSuccess() |
295 | { |
296 | // TODO: |
297 | } |
298 | |
299 | void QBluetoothTransferReplyOSXPrivate::OBEXPutDataSent(quint32 current, quint32 total) |
300 | { |
301 | emit q_ptr->transferProgress(current, total); |
302 | } |
303 | |
304 | void QBluetoothTransferReplyOSXPrivate::OBEXPutSuccess() |
305 | { |
306 | requestComplete = true; |
307 | emit q_ptr->finished(q_ptr); |
308 | } |
309 | |
310 | void 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 | |
329 | void 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 | |
342 | QBluetoothTransferReplyOSX::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 | |
367 | QBluetoothTransferReplyOSX::~QBluetoothTransferReplyOSX() |
368 | { |
369 | // A dtor to make a scoped pointer with incomplete type happy. |
370 | } |
371 | |
372 | QBluetoothTransferReply::TransferError QBluetoothTransferReplyOSX::error() const |
373 | { |
374 | return osx_d_ptr->error; |
375 | } |
376 | |
377 | QString QBluetoothTransferReplyOSX::errorString() const |
378 | { |
379 | return osx_d_ptr->errorString; |
380 | } |
381 | |
382 | bool QBluetoothTransferReplyOSX::isFinished() const |
383 | { |
384 | return osx_d_ptr->requestComplete; |
385 | } |
386 | |
387 | bool QBluetoothTransferReplyOSX::isRunning() const |
388 | { |
389 | return osx_d_ptr->isActive(); |
390 | } |
391 | |
392 | bool 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 | |
409 | bool 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 | |
433 | void 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 | |
456 | void 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 | |
477 | QT_END_NAMESPACE |
478 |
Warning: That file was not part of the compilation database. It may have many parsing errors.