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