1 /**
2 * This file is part of TelepathyQt
3 *
4 * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
5 * @copyright Copyright (C) 2009 Nokia Corporation
6 * @license LGPL 2.1
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23 #include <TelepathyQt/FileTransferChannel>
24
25 #include "TelepathyQt/_gen/file-transfer-channel.moc.hpp"
26
27 #include "TelepathyQt/debug-internal.h"
28
29 #include <TelepathyQt/Connection>
30 #include <TelepathyQt/Types>
31
32 namespace Tp
33 {
34
35 struct TP_QT_NO_EXPORT FileTransferChannel::Private
36 {
37 Private(FileTransferChannel *parent);
38 ~Private();
39
40 static void introspectProperties(Private *self);
41
42 void extractProperties(const QVariantMap &props);
43
44 // Public object
45 FileTransferChannel *parent;
46
47 Client::ChannelTypeFileTransferInterface *fileTransferInterface;
48 Client::DBus::PropertiesInterface *properties;
49
50 ReadinessHelper *readinessHelper;
51
52 // Introspection
53 uint pendingState;
54 uint pendingStateReason;
55 uint state;
56 uint stateReason;
57 QString contentType;
58 QString fileName;
59 QString uri;
60 QString contentHash;
61 QString description;
62 QDateTime lastModificationTime;
63 FileHashType contentHashType;
64 qulonglong initialOffset;
65 qulonglong size;
66 qulonglong transferredBytes;
67 SupportedSocketMap availableSocketTypes;
68
69 bool connected;
70 bool finished;
71 };
72
Private(FileTransferChannel * parent)73 FileTransferChannel::Private::Private(FileTransferChannel *parent)
74 : parent(parent),
75 fileTransferInterface(parent->interface<Client::ChannelTypeFileTransferInterface>()),
76 properties(parent->interface<Client::DBus::PropertiesInterface>()),
77 readinessHelper(parent->readinessHelper()),
78 pendingState(FileTransferStateNone),
79 pendingStateReason(FileTransferStateChangeReasonNone),
80 state(pendingState),
81 stateReason(pendingStateReason),
82 contentHashType(FileHashTypeNone),
83 initialOffset(0),
84 size(0),
85 transferredBytes(0),
86 connected(false),
87 finished(false)
88 {
89 parent->connect(fileTransferInterface,
90 SIGNAL(InitialOffsetDefined(qulonglong)),
91 SLOT(onInitialOffsetDefined(qulonglong)));
92 parent->connect(fileTransferInterface,
93 SIGNAL(FileTransferStateChanged(uint,uint)),
94 SLOT(onStateChanged(uint,uint)));
95 parent->connect(fileTransferInterface,
96 SIGNAL(TransferredBytesChanged(qulonglong)),
97 SLOT(onTransferredBytesChanged(qulonglong)));
98
99 ReadinessHelper::Introspectables introspectables;
100
101 ReadinessHelper::Introspectable introspectableCore(
102 QSet<uint>() << 0, // makesSenseForStatuses
103 Features() << Channel::FeatureCore, // dependsOnFeatures (core)
104 QStringList(), // dependsOnInterfaces
105 (ReadinessHelper::IntrospectFunc) &Private::introspectProperties,
106 this);
107 introspectables[FeatureCore] = introspectableCore;
108
109 readinessHelper->addIntrospectables(introspectables);
110 }
111
~Private()112 FileTransferChannel::Private::~Private()
113 {
114 }
115
introspectProperties(FileTransferChannel::Private * self)116 void FileTransferChannel::Private::introspectProperties(
117 FileTransferChannel::Private *self)
118 {
119 QDBusPendingCallWatcher *watcher =
120 new QDBusPendingCallWatcher(
121 self->properties->GetAll(
122 TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER),
123 self->parent);
124 self->parent->connect(watcher,
125 SIGNAL(finished(QDBusPendingCallWatcher*)),
126 SLOT(gotProperties(QDBusPendingCallWatcher*)));
127 }
128
extractProperties(const QVariantMap & props)129 void FileTransferChannel::Private::extractProperties(const QVariantMap &props)
130 {
131 pendingState = state = qdbus_cast<uint>(props[QLatin1String("State")]);
132 contentType = qdbus_cast<QString>(props[QLatin1String("ContentType")]);
133 fileName = qdbus_cast<QString>(props[QLatin1String("Filename")]);
134 uri = qdbus_cast<QString>(props[QLatin1String("URI")]);
135 contentHash = qdbus_cast<QString>(props[QLatin1String("ContentHash")]);
136 description = qdbus_cast<QString>(props[QLatin1String("Description")]);
137 lastModificationTime.setTime_t((uint) qdbus_cast<qulonglong>(props[QLatin1String("Date")]));
138 contentHashType = (FileHashType) qdbus_cast<uint>(props[QLatin1String("ContentHashType")]);
139 initialOffset = qdbus_cast<qulonglong>(props[QLatin1String("InitialOffset")]);
140 size = qdbus_cast<qulonglong>(props[QLatin1String("Size")]);
141 transferredBytes = qdbus_cast<qulonglong>(props[QLatin1String("TransferredBytes")]);
142 availableSocketTypes = qdbus_cast<SupportedSocketMap>(props[QLatin1String("AvailableSocketTypes")]);
143 }
144
145 /**
146 * \class FileTransferChannel
147 * \ingroup clientchannel
148 * \headerfile TelepathyQt/file-transfer-channel.h <TelepathyQt/FileTransferChannel>
149 *
150 * \brief The FileTransferChannel class represents a Telepathy channel of type
151 * FileTransfer.
152 *
153 * For more specialized file transfer classes, please refer to
154 * OutgoingFileTransferChannel and IncomingFileTransferChannel.
155 *
156 * For more details, please refer to \telepathy_spec.
157 *
158 * See \ref async_model, \ref shared_ptr
159 */
160
161 /**
162 * Feature representing the core that needs to become ready to make the
163 * FileTransferChannel object usable.
164 *
165 * Note that this feature must be enabled in order to use most
166 * FileTransferChannel methods.
167 * See specific methods documentation for more details.
168 *
169 * When calling isReady(), becomeReady(), this feature is implicitly added
170 * to the requested features.
171 */
172 const Feature FileTransferChannel::FeatureCore = Feature(QLatin1String(FileTransferChannel::staticMetaObject.className()), 0);
173
174 /**
175 * Create a new FileTransferChannel object.
176 *
177 * \param connection Connection owning this channel, and specifying the
178 * service.
179 * \param objectPath The channel object path.
180 * \param immutableProperties The channel immutable properties.
181 * \return A FileTransferChannelPtr object pointing to the newly created
182 * FileTransferChannel object.
183 */
create(const ConnectionPtr & connection,const QString & objectPath,const QVariantMap & immutableProperties)184 FileTransferChannelPtr FileTransferChannel::create(const ConnectionPtr &connection,
185 const QString &objectPath, const QVariantMap &immutableProperties)
186 {
187 return FileTransferChannelPtr(new FileTransferChannel(connection, objectPath,
188 immutableProperties, FileTransferChannel::FeatureCore));
189 }
190
191 /**
192 * Construct a new FileTransferChannel object.
193 *
194 * \param connection Connection owning this channel, and specifying the
195 * service.
196 * \param objectPath The channel object path.
197 * \param immutableProperties The channel immutable properties.
198 * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should
199 * depend on FileTransferChannel::FeatureCore.
200 */
FileTransferChannel(const ConnectionPtr & connection,const QString & objectPath,const QVariantMap & immutableProperties,const Feature & coreFeature)201 FileTransferChannel::FileTransferChannel(const ConnectionPtr &connection,
202 const QString &objectPath,
203 const QVariantMap &immutableProperties,
204 const Feature &coreFeature)
205 : Channel(connection, objectPath, immutableProperties, coreFeature),
206 mPriv(new Private(this))
207 {
208 }
209
210 /**
211 * Class destructor.
212 */
~FileTransferChannel()213 FileTransferChannel::~FileTransferChannel()
214 {
215 delete mPriv;
216 }
217
218 /**
219 * Return the state of the file transfer as described by #FileTransferState.
220 *
221 * Change notification is via the stateChanged() signal.
222 *
223 * This method requires FileTransferChannel::FeatureCore to be ready.
224 *
225 * \return The state as #FileTransferState.
226 * \sa stateReason()
227 */
state() const228 FileTransferState FileTransferChannel::state() const
229 {
230 if (!isReady(FeatureCore)) {
231 warning() << "FileTransferChannel::FeatureCore must be ready before "
232 "calling state";
233 }
234
235 return (FileTransferState) mPriv->state;
236 }
237
238 /**
239 * Return the reason for the state change as described by the #FileTransferStateChangeReason.
240 *
241 * Change notification is via the stateChanged() signal.
242 *
243 * This method requires FileTransferChannel::FeatureCore to be ready.
244 *
245 * \return The state reason as #FileTransferStateChangeReason.
246 * \sa state()
247 */
stateReason() const248 FileTransferStateChangeReason FileTransferChannel::stateReason() const
249 {
250 if (!isReady(FeatureCore)) {
251 warning() << "FileTransferChannel::FeatureCore must be ready before "
252 "calling stateReason";
253 }
254
255 return (FileTransferStateChangeReason) mPriv->stateReason;
256 }
257
258 /**
259 * Return the name of the file on the sender's side. This is given as
260 * a suggested filename for the receiver.
261 *
262 * This property should be the basename of the file being sent. For example, if
263 * the sender sends the file /home/user/monkey.pdf then this property should be
264 * set to monkey.pdf.
265 *
266 * This property cannot change once the channel has been created.
267 *
268 * This method requires FileTransferChannel::FeatureCore to be ready.
269 *
270 * \return The suggested filename for the receiver.
271 */
fileName() const272 QString FileTransferChannel::fileName() const
273 {
274 if (!isReady(FeatureCore)) {
275 warning() << "FileTransferChannel::FeatureCore must be ready before "
276 "calling fileName";
277 }
278
279 return mPriv->fileName;
280 }
281
282 /**
283 * Return the file's MIME type.
284 *
285 * This property cannot change once the channel has been created.
286 *
287 * This method requires FileTransferChannel::FeatureCore to be ready.
288 *
289 * \return The file's MIME type.
290 */
contentType() const291 QString FileTransferChannel::contentType() const
292 {
293 if (!isReady(FeatureCore)) {
294 warning() << "FileTransferChannel::FeatureCore must be ready before "
295 "calling contentType";
296 }
297
298 return mPriv->contentType;
299 }
300
301 /**
302 * Return the size of the file.
303 *
304 * Note that the size is not guaranteed to be exactly right for
305 * incoming files. This is merely a hint and should not be used to know when the
306 * transfer finished.
307 *
308 * For unknown sizes the return value can be UINT64_MAX.
309 *
310 * This property cannot change once the channel has been created.
311 *
312 * This method requires FileTransferChannel::FeatureCore to be ready.
313 *
314 * \return The file size.
315 */
size() const316 qulonglong FileTransferChannel::size() const
317 {
318 if (!isReady(FeatureCore)) {
319 warning() << "FileTransferChannel::FeatureCore must be ready before "
320 "calling size";
321 }
322
323 return mPriv->size;
324 }
325
326 /**
327 * Return the URI of the file.
328 *
329 * On outgoing file transfers, this property cannot change after the channel
330 * is requested. For incoming file transfers, this property may be set by the
331 * channel handler before calling AcceptFile to inform observers where the
332 * incoming file will be saved. When the URI property is set, the signal
333 * IncomingFileTransferChannel::uriDefined() is emitted.
334 *
335 * This method requires FileTransferChannel::FeatureCore to be ready.
336 *
337 * \return The file uri.
338 * \sa IncomingFileTransferChannel::uriDefined()
339 */
uri() const340 QString FileTransferChannel::uri() const
341 {
342 if (!isReady(FeatureCore)) {
343 warning() << "FileTransferChannel::FeatureCore must be ready before "
344 "calling uri";
345 }
346
347 return mPriv->uri;
348 }
349
350 /**
351 * Return the type of the contentHash().
352 *
353 * This method requires FileTransferChannel::FeatureCore to be ready.
354 *
355 * \return The content hash type as #FileHashType.
356 * \sa contentHash()
357 */
contentHashType() const358 FileHashType FileTransferChannel::contentHashType() const
359 {
360 if (!isReady(FeatureCore)) {
361 warning() << "FileTransferChannel::FeatureCore must be ready before "
362 "calling contentHashType";
363 }
364
365 return mPriv->contentHashType;
366 }
367
368 /**
369 * Return the hash of the contents of the file transfer, of type described in
370 * the value of the contentHashType().
371 *
372 * Its value MUST correspond to the appropriate type of the contentHashType().
373 * If the contentHashType() is set to #FileHashTypeNone, then the
374 * returned value is an empty string.
375 *
376 * This method requires FileTransferChannel::FeatureCore to be ready.
377 *
378 * \return The hash of the contents.
379 * \sa contentHashType()
380 */
contentHash() const381 QString FileTransferChannel::contentHash() const
382 {
383 if (!isReady(FeatureCore)) {
384 warning() << "FileTransferChannel::FeatureCore must be ready before "
385 "calling contentHash";
386 }
387
388 if (mPriv->contentHashType == FileHashTypeNone) {
389 return QString();
390 }
391
392 return mPriv->contentHash;
393 }
394
395 /**
396 * Return the description of the file transfer.
397 *
398 * This property cannot change once the channel has been created.
399 *
400 * This method requires FileTransferChannel::FeatureCore to be ready.
401 *
402 * \return The description.
403 */
description() const404 QString FileTransferChannel::description() const
405 {
406 if (!isReady(FeatureCore)) {
407 warning() << "FileTransferChannel::FeatureCore must be ready before "
408 "calling description";
409 }
410
411 return mPriv->description;
412 }
413
414 /**
415 * Return the last modification time of the file being transferred. This cannot
416 * change once the channel has been created.
417 *
418 * This method requires FileTransferChannel::FeatureCore to be ready.
419 *
420 * \return The file modification time as QDateTime.
421 */
lastModificationTime() const422 QDateTime FileTransferChannel::lastModificationTime() const
423 {
424 if (!isReady(FeatureCore)) {
425 warning() << "FileTransferChannel::FeatureCore must be ready before "
426 "calling lastModificationTime";
427 }
428
429 return mPriv->lastModificationTime;
430 }
431
432 /**
433 * Return the offset in bytes from which the file will be sent.
434 *
435 * This method requires FileTransferChannel::FeatureCore to be ready.
436 *
437 * \return The offset in bytes.
438 * \sa initialOffsetDefined()
439 */
initialOffset() const440 qulonglong FileTransferChannel::initialOffset() const
441 {
442 if (!isReady(FeatureCore)) {
443 warning() << "FileTransferChannel::FeatureCore must be ready before "
444 "calling initialOffset";
445 }
446
447 return mPriv->initialOffset;
448 }
449
450 /**
451 * Return the number of bytes that have been transferred.
452 *
453 * Change notification is via the transferredBytesChanged() signal.
454 *
455 * This method requires FileTransferChannel::FeatureCore to be ready.
456 *
457 * \return The number of bytes.
458 * \sa transferredBytesChanged()
459 */
transferredBytes() const460 qulonglong FileTransferChannel::transferredBytes() const
461 {
462 if (!isReady(FeatureCore)) {
463 warning() << "FileTransferChannel::FeatureCore must be ready before "
464 "calling transferredBytes";
465 }
466
467 return mPriv->transferredBytes;
468 }
469
470 /**
471 * Return a mapping from address types (members of #SocketAddressType) to arrays
472 * of access-control type (members of #SocketAccessControl) that the CM
473 * supports for sockets with that address type.
474 *
475 * For simplicity, if a CM supports offering a particular type of file transfer,
476 * it is assumed to support accepting it. All CMs support at least
477 * SocketAddressTypeIPv4.
478 *
479 * This method requires FileTransferChannel::FeatureCore to be ready.
480 *
481 * \return The available socket types as a map from address types to arrays of access-control type.
482 */
availableSocketTypes() const483 SupportedSocketMap FileTransferChannel::availableSocketTypes() const
484 {
485 if (!isReady(FeatureCore)) {
486 warning() << "FileTransferChannel::FeatureCore must be ready before "
487 "calling availableSocketTypes";
488 }
489
490 return mPriv->availableSocketTypes;
491 }
492
493 /**
494 * Cancel a file transfer.
495 *
496 * \return A PendingOperation object which will emit PendingOperation::finished
497 * when the call has finished.
498 */
cancel()499 PendingOperation *FileTransferChannel::cancel()
500 {
501 return requestClose();
502 }
503
504 /**
505 * Protected virtual method called when the state becomes
506 * #FileTransferStateOpen.
507 *
508 * Specialized classes should reimplement this method and call setConnected()
509 * when the connection is established.
510 *
511 * \sa setConnected()
512 */
connectToHost()513 void FileTransferChannel::connectToHost()
514 {
515 // do nothing
516 }
517
518 /**
519 * Return whether a connection has been established.
520 *
521 * \return \c true if the connections has been established, \c false otherwise.
522 * \sa setConnected()
523 */
isConnected() const524 bool FileTransferChannel::isConnected() const
525 {
526 return mPriv->connected;
527 }
528
529 /**
530 * Indicate whether a connection has been established.
531 *
532 * Specialized classes that reimplement connectToHost() must call this method
533 * once the connection has been established or setFinished() if an error
534 * occurred.
535 *
536 * \sa isConnected(), connectToHost(), setFinished()
537 */
setConnected()538 void FileTransferChannel::setConnected()
539 {
540 mPriv->connected = true;
541 }
542
543 /**
544 * Return whether sending/receiving has finished.
545 *
546 * \return \c true if sending/receiving has finished, \c false otherwise.
547 */
isFinished() const548 bool FileTransferChannel::isFinished() const
549 {
550 return mPriv->finished;
551 }
552
553 /**
554 * Protected virtual method called when an error occurred and the transfer
555 * should finish.
556 *
557 * Specialized classes should reimplement this method and close the IO devices
558 * and do all the needed cleanup.
559 *
560 * Note that for specialized classes that reimplement connectToHost() and set
561 * isConnected() to true, the state will not change to
562 * #FileTransferStateCompleted once the state change is received.
563 *
564 * When finished sending/receiving the specialized class MUST call this method
565 * and then the state will change to the latest pending state.
566 */
setFinished()567 void FileTransferChannel::setFinished()
568 {
569 mPriv->finished = true;
570
571 // do the actual state change, in case we are in
572 // FileTransferStateCompleted pendingState
573 changeState();
574 }
575
gotProperties(QDBusPendingCallWatcher * watcher)576 void FileTransferChannel::gotProperties(QDBusPendingCallWatcher *watcher)
577 {
578 QDBusPendingReply<QVariantMap> reply = *watcher;
579
580 if (!reply.isError()) {
581 QVariantMap props = reply.value();
582 mPriv->extractProperties(props);
583 debug() << "Got reply to Properties::GetAll(FileTransferChannel)";
584 mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
585 }
586 else {
587 warning().nospace() << "Properties::GetAll(FileTransferChannel) failed "
588 "with " << reply.error().name() << ": " << reply.error().message();
589 mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false,
590 reply.error());
591 }
592 }
593
changeState()594 void FileTransferChannel::changeState()
595 {
596 if (mPriv->state == mPriv->pendingState) {
597 return;
598 }
599
600 mPriv->state = mPriv->pendingState;
601 mPriv->stateReason = mPriv->pendingStateReason;
602 emit stateChanged((FileTransferState) mPriv->state,
603 (FileTransferStateChangeReason) mPriv->stateReason);
604 }
605
onStateChanged(uint state,uint stateReason)606 void FileTransferChannel::onStateChanged(uint state, uint stateReason)
607 {
608 if (state == (uint) mPriv->pendingState) {
609 return;
610 }
611
612 debug() << "File transfer state changed to" << state <<
613 "with reason" << stateReason;
614 mPriv->pendingState = (FileTransferState) state;
615 mPriv->pendingStateReason = (FileTransferStateChangeReason) stateReason;
616
617 switch (state) {
618 case FileTransferStateOpen:
619 // try to connect to host, for handlers this
620 // connect to host, as the user called Accept/ProvideFile
621 // and have the host addr, for observers this will do nothing and
622 // everything will keep working
623 connectToHost();
624 changeState();
625 break;
626 case FileTransferStateCompleted:
627 //iIf already finished sending/receiving, just change the state,
628 // if not completed will only be set when:
629 // IncomingChannel:
630 // - The input socket closes
631 // OutgoingChannel:
632 // - Input EOF is reached or the output socket is closed
633 //
634 // we also check for connected as observers will never be connected
635 // and finished will never be set, but we need to work anyway.
636 if (mPriv->finished || !mPriv->connected) {
637 changeState();
638 }
639 break;
640 case FileTransferStateCancelled:
641 // if already finished sending/receiving, just change the state,
642 // if not finish now and change the state
643 if (!mPriv->finished) {
644 setFinished();
645 } else {
646 changeState();
647 }
648 break;
649 default:
650 changeState();
651 break;
652 }
653 }
654
onInitialOffsetDefined(qulonglong initialOffset)655 void FileTransferChannel::onInitialOffsetDefined(qulonglong initialOffset)
656 {
657 mPriv->initialOffset = initialOffset;
658 emit initialOffsetDefined(initialOffset);
659 }
660
onTransferredBytesChanged(qulonglong count)661 void FileTransferChannel::onTransferredBytesChanged(qulonglong count)
662 {
663 mPriv->transferredBytes = count;
664 emit transferredBytesChanged(count);
665 }
666
onUriDefined(const QString & uri)667 void FileTransferChannel::onUriDefined(const QString &uri)
668 {
669 mPriv->uri = uri;
670 // Signal is emitted only by IncomingFileTransferChannels
671 }
672
673 /**
674 * \fn void FileTransferChannel::stateChanged(Tp::FileTransferState state,
675 * Tp::FileTransferStateChangeReason reason)
676 *
677 * Emitted when the value of state() changes.
678 *
679 * \param state The new state of this file transfer channel.
680 * \param reason The reason for the change of state.
681 * \sa state()
682 */
683
684 /**
685 * \fn void FileTransferChannel::initialOffsetDefined(qulonglong initialOffset)
686 *
687 * Emitted when the initial offset for the file transfer is
688 * defined.
689 *
690 * \param initialOffset The new initial offset for the file transfer.
691 * \sa initialOffset()
692 */
693
694 /**
695 * \fn void FileTransferChannel::transferredBytesChanged(qulonglong count);
696 *
697 * Emitted when the value of transferredBytes() changes.
698 *
699 * \param count The new number of bytes transferred.
700 * \sa transferredBytes()
701 */
702
703 } // Tp
704