[telepathy-qt4/master] FileTransferChannel: Added support for full-featured FileTransfer support.
Andre Moreira Magalhaes (andrunko)
andre.magalhaes at collabora.co.uk
Mon Sep 28 19:03:42 PDT 2009
---
TelepathyQt4/file-transfer-channel.cpp | 479 +++++++++++++++++++++++++++++++-
TelepathyQt4/file-transfer-channel.h | 37 +++
2 files changed, 511 insertions(+), 5 deletions(-)
diff --git a/TelepathyQt4/file-transfer-channel.cpp b/TelepathyQt4/file-transfer-channel.cpp
index 7f60456..ea74bec 100644
--- a/TelepathyQt4/file-transfer-channel.cpp
+++ b/TelepathyQt4/file-transfer-channel.cpp
@@ -21,31 +21,223 @@
#include <TelepathyQt4/FileTransferChannel>
#include "TelepathyQt4/_gen/file-transfer-channel.moc.hpp"
+
#include "TelepathyQt4/debug-internal.h"
#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/PendingVariant>
+#include <TelepathyQt4/Types>
+
+#include <QIODevice>
+#include <QTcpSocket>
+
+#define BUFFER_SIZE 4096
namespace Tp
{
struct FileTransferChannel::Private
{
- inline Private();
- inline ~Private();
+ Private(FileTransferChannel *parent);
+ ~Private();
+
+ static void introspectProperties(Private *self);
+
+ void extractProperties(const QVariantMap &props);
+
+ void connectToHost();
+ void startTransfer();
+ void writeData();
+ void changeState();
+ void setFinished();
+
+ // Public object
+ FileTransferChannel *parent;
+
+ Client::ChannelTypeFileTransferInterface *fileTransferInterface;
+ Client::DBus::PropertiesInterface *properties;
+
+ ReadinessHelper *readinessHelper;
+
+ // Introspection
+ FileTransferState pendingState;
+ FileTransferStateChangeReason pendingStateReason;
+ FileTransferState state;
+ FileTransferStateChangeReason stateReason;
+ QString contentType;
+ QString fileName;
+ QString contentHash;
+ QString description;
+ QDateTime lastModificationTime;
+ FileHashType contentHashType;
+ qulonglong size;
+ qulonglong transferredBytes;
+ qulonglong initialOffset;
+ qulonglong bytesWritten;
+
+ QIODevice *input;
+ QIODevice *output;
+ QTcpSocket *socket;
+ SocketAddressIPv4 addr;
};
-FileTransferChannel::Private::Private()
+FileTransferChannel::Private::Private(FileTransferChannel *parent)
+ : parent(parent),
+ fileTransferInterface(parent->fileTransferInterface(BypassInterfaceCheck)),
+ properties(0),
+ readinessHelper(parent->readinessHelper()),
+ pendingState(FileTransferStateNone),
+ pendingStateReason(FileTransferStateChangeReasonNone),
+ state(pendingState),
+ stateReason(pendingStateReason),
+ contentHashType(FileHashTypeNone),
+ size(0),
+ transferredBytes(0),
+ initialOffset(0),
+ bytesWritten(0),
+ input(0),
+ output(0),
+ socket(0)
{
+ parent->connect(fileTransferInterface,
+ SIGNAL(FileTransferStateChanged(uint, uint)),
+ SLOT(onFileTrasnferStateChanged(uint, uint)));
+ parent->connect(fileTransferInterface,
+ SIGNAL(TransferredBytesChanged(qulonglong)),
+ SIGNAL(transferredBytesChanged(qulonglong)));
+ parent->connect(fileTransferInterface,
+ SIGNAL(InitialOffsetDefined(qulonglong)),
+ SIGNAL(initialOffsetDefined(qulonglong)));
+
+ ReadinessHelper::Introspectables introspectables;
+
+ ReadinessHelper::Introspectable introspectableCore(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << Channel::FeatureCore, // dependsOnFeatures (core)
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectProperties,
+ this);
+ introspectables[FeatureCore] = introspectableCore;
+
+ readinessHelper->addIntrospectables(introspectables);
}
FileTransferChannel::Private::~Private()
{
}
+void FileTransferChannel::Private::introspectProperties(
+ FileTransferChannel::Private *self)
+{
+ if (!self->properties) {
+ self->properties = self->parent->propertiesInterface();
+ Q_ASSERT(self->properties != 0);
+ }
+
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ self->properties->GetAll(
+ TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER),
+ self->parent);
+ self->parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher *)),
+ SLOT(gotProperties(QDBusPendingCallWatcher *)));
+}
+
+void FileTransferChannel::Private::extractProperties(const QVariantMap &props)
+{
+ state = (FileTransferState) qdbus_cast<uint>(props["State"]);
+ pendingState = state;
+ contentType = qdbus_cast<QString>(props["ContentType"]);
+ fileName = qdbus_cast<QString>(props["Filename"]);
+ contentHash = qdbus_cast<QString>(props["ContentHash"]);
+ description = qdbus_cast<QString>(props["Description"]);
+ lastModificationTime.setTime_t((uint) qdbus_cast<qulonglong>(props["Date"]));
+ contentHashType = (FileHashType) qdbus_cast<uint>(props["ContentHashType"]);
+ size = qdbus_cast<qulonglong>(props["Size"]);
+ transferredBytes = qdbus_cast<qulonglong>(props["TransferredBytes"]);
+ initialOffset = qdbus_cast<qulonglong>(props["InitialOffset"]);
+}
+
+void FileTransferChannel::Private::connectToHost()
+{
+ socket = new QTcpSocket(parent);
+
+ if (parent->isRequested()) {
+ output = socket;
+ } else {
+ input = socket;
+ }
+
+ parent->connect(socket,
+ SIGNAL(connected()),
+ SLOT(onSocketConnected()));
+ debug().nospace() << "Connecting to host " <<
+ addr.address << ":" << addr.port << "...";
+
+ socket->connectToHost(addr.address, addr.port);
+}
+
+void FileTransferChannel::Private::startTransfer()
+{
+ parent->connect(input, SIGNAL(readyRead()), SLOT(writeData()));
+ parent->writeData();
+}
+
+void FileTransferChannel::Private::writeData()
+{
+ char buffer[16 * 1024];
+ qint64 len = input->read(buffer, sizeof(buffer));
+ if (len > 0) {
+ output->write(buffer, len); // never fails
+ bytesWritten += len;
+ } else if (len == -1 || (!input->isSequential() && input->atEnd())) {
+ // error or EOF
+ if (pendingState == FileTransferStateCompleted &&
+ bytesWritten != size) {
+ // the CM finished receiving the file but some error occurred
+ pendingState = FileTransferStateCancelled;
+ pendingStateReason = FileTransferStateChangeReasonLocalError;
+ warning() << "An error occurred during the transfer";
+ }
+ setFinished();
+ return;
+ }
+
+ if (pendingState == FileTransferStateCompleted &&
+ bytesWritten == size) {
+ setFinished();
+ return;
+ }
+
+ // if there is no data available, writeData will be called automatically
+ // when readyRead is emitted
+ if (input->bytesAvailable() > 0) {
+ // process more data
+ QMetaObject::invokeMethod(parent, "writeData", Qt::QueuedConnection);
+ }
+}
+
+void FileTransferChannel::Private::changeState()
+{
+ state = pendingState;
+ stateReason = pendingStateReason;
+ debug() << "State changed to" << state << "with reason" << stateReason;
+ emit parent->stateChanged(state, stateReason);
+}
+
+void FileTransferChannel::Private::setFinished()
+{
+ changeState();
+ input->close();
+ output->close();
+}
+
/**
* \class FileTransferChannel
* \ingroup clientchannel
- * \headerfile TelepathyQt4/file-transfer-channel.h <TelepathyQt4/FileTransferChannel>
+ * \headerfile <TelepathyQt4/file-transfer-channel.h> <TelepathyQt4/FileTransferChannel>
*
* High-level proxy object for accessing remote %Channel objects of the
* FileTransfer channel type. These channels can be used to transfer one file
@@ -55,6 +247,8 @@ FileTransferChannel::Private::~Private()
* FileTransfer interface. Until then, it's just a Channel.
*/
+const Feature FileTransferChannel::FeatureCore = Feature(FileTransferChannel::staticMetaObject.className(), 0);
+
FileTransferChannelPtr FileTransferChannel::create(const ConnectionPtr &connection,
const QString &objectPath, const QVariantMap &immutableProperties)
{
@@ -77,7 +271,7 @@ FileTransferChannel::FileTransferChannel(const ConnectionPtr &connection,
const QString &objectPath,
const QVariantMap &immutableProperties)
: Channel(connection, objectPath, immutableProperties),
- mPriv(new Private())
+ mPriv(new Private(this))
{
}
@@ -89,4 +283,279 @@ FileTransferChannel::~FileTransferChannel()
delete mPriv;
}
+FileTransferState FileTransferChannel::state() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling state";
+ }
+
+ return mPriv->state;
+}
+
+FileTransferStateChangeReason FileTransferChannel::stateReason() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling stateReason";
+ }
+
+ return mPriv->stateReason;
+}
+
+QString FileTransferChannel::fileName() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling fileName";
+ }
+
+ return mPriv->fileName;
+}
+
+QString FileTransferChannel::contentType() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling contentType";
+ }
+
+ return mPriv->contentType;
+}
+
+qulonglong FileTransferChannel::size() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling size";
+ }
+
+ return mPriv->size;
+}
+
+FileHashType FileTransferChannel::contentHashType() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling contentHashType";
+ }
+
+ return mPriv->contentHashType;
+}
+
+QString FileTransferChannel::contentHash() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling contentHash";
+ }
+
+ return mPriv->contentHash;
+}
+
+QString FileTransferChannel::description() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling description";
+ }
+
+ return mPriv->description;
+}
+
+QDateTime FileTransferChannel::lastModificationTime() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling lastModificationTime";
+ }
+
+ return mPriv->lastModificationTime;
+}
+
+qulonglong FileTransferChannel::initialOffset() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling initialOffset";
+ }
+
+ return mPriv->initialOffset;
+}
+
+qulonglong FileTransferChannel::transferredBytes() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling transferredBytes";
+ }
+
+ return mPriv->transferredBytes;
+}
+
+PendingOperation *FileTransferChannel::provideFile(QIODevice *input)
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling provideFile";
+ return new PendingFailure(this, TELEPATHY_ERROR_NOT_AVAILABLE,
+ "Channel not ready");
+ }
+
+ if (!isRequested()) {
+ warning() << "Channel must have been requested in order to start an "
+ "outgoing file transfer";
+ return new PendingFailure(this, TELEPATHY_ERROR_NOT_YOURS,
+ "Channel must have been requested in order to start an "
+ "outgoing file transfer");
+ }
+
+ // let's fail here direclty as we may only have one device to handle
+ if (mPriv->input) {
+ warning() << "File transfer can only be started once in the same "
+ "channel";
+ return new PendingFailure(this, TELEPATHY_ERROR_NOT_AVAILABLE,
+ "File transfer can only be started once in the same channel");
+ }
+
+ if ((!input->isOpen() && !input->open(QIODevice::ReadOnly)) &&
+ !input->isReadable()) {
+ warning() << "Unable to open IO device for reading";
+ return new PendingFailure(this, TELEPATHY_ERROR_PERMISSION_DENIED,
+ "Unable to open IO device for reading");
+ }
+
+ mPriv->input = input;
+
+ PendingVariant *pv = new PendingVariant(
+ mPriv->fileTransferInterface->ProvideFile(SocketAddressTypeIPv4,
+ SocketAccessControlLocalhost, QDBusVariant(QVariant(QString()))),
+ this);
+ connect(pv,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onCallFinished(Tp::PendingOperation*)));
+ return pv;
+}
+
+PendingOperation *FileTransferChannel::acceptFile(qulonglong offset,
+ QIODevice *output)
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling acceptFile";
+ return new PendingFailure(this, TELEPATHY_ERROR_NOT_AVAILABLE,
+ "Channel not ready");
+ }
+
+ if (isRequested()) {
+ warning() << "Channel must not have been requested in order to accept "
+ "an incoming file transfer";
+ return new PendingFailure(this, TELEPATHY_ERROR_NOT_YOURS,
+ "Channel must not have been requested in order to accept an "
+ "incoming file transfer");
+ }
+
+ // let's fail here direclty as we may only have one device to handle
+ if (mPriv->output) {
+ warning() << "File transfer can only be started once in the same "
+ "channel";
+ return new PendingFailure(this, TELEPATHY_ERROR_NOT_AVAILABLE,
+ "File transfer can only be started once in the same channel");
+ }
+
+ if ((!output->isOpen() && !output->open(QIODevice::ReadWrite)) &&
+ !output->isWritable()) {
+ warning() << "Unable to open IO device for writing";
+ return new PendingFailure(this, TELEPATHY_ERROR_PERMISSION_DENIED,
+ "Unable to open IO device for writing");
+ }
+
+ mPriv->output = output;
+
+ PendingVariant *pv = new PendingVariant(
+ mPriv->fileTransferInterface->AcceptFile(SocketAddressTypeIPv4,
+ SocketAccessControlLocalhost, QDBusVariant(QVariant(QString())),
+ offset),
+ this);
+ connect(pv,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onCallFinished(Tp::PendingOperation*)));
+ return pv;
+}
+
+void FileTransferChannel::cancel()
+{
+ requestClose();
+}
+
+void FileTransferChannel::gotProperties(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+
+ if (!reply.isError()) {
+ QVariantMap props = reply.value();
+ mPriv->extractProperties(props);
+ debug() << "Got reply to Properties::GetAll(FileTransferChannel)";
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ }
+ else {
+ warning().nospace() << "Properties::GetAll(FileTransferChannel) failed "
+ "with " << reply.error().name() << ": " << reply.error().message();
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false,
+ reply.error());
+ }
+}
+
+void FileTransferChannel::onCallFinished(PendingOperation *op)
+{
+ if (op->isError()) {
+ // fail providing file, request close
+ warning() << "Error starting file transfer, "
+ "closing channel...";
+ requestClose();
+ return;
+ }
+
+ PendingVariant *pv = qobject_cast<PendingVariant *>(op);
+ mPriv->addr = qdbus_cast<SocketAddressIPv4>(pv->result());
+ debug().nospace() << "Got address " << mPriv->addr.address <<
+ ":" << mPriv->addr.port;
+
+ if (mPriv->pendingState == FileTransferStateOpen) {
+ mPriv->connectToHost();
+ }
+}
+
+void FileTransferChannel::onFileTrasnferStateChanged(uint state,
+ uint stateReason)
+{
+ if (state == (uint) mPriv->pendingState) {
+ return;
+ }
+
+ mPriv->pendingState = (FileTransferState) state;
+ mPriv->pendingStateReason = (FileTransferStateChangeReason) stateReason;
+
+ if (state == FileTransferStateOpen && !mPriv->addr.address.isNull()) {
+ mPriv->connectToHost();
+ }
+
+ if ((state != FileTransferStateCompleted) ||
+ (state == FileTransferStateCompleted &&
+ mPriv->bytesWritten == mPriv->size)) {
+ mPriv->changeState();
+ }
+}
+
+void FileTransferChannel::onSocketConnected()
+{
+ debug() << "Connected to host!";
+ mPriv->startTransfer();
+}
+
+void FileTransferChannel::writeData()
+{
+ mPriv->writeData();
+}
+
} // Tp
diff --git a/TelepathyQt4/file-transfer-channel.h b/TelepathyQt4/file-transfer-channel.h
index 956f409..8143621 100644
--- a/TelepathyQt4/file-transfer-channel.h
+++ b/TelepathyQt4/file-transfer-channel.h
@@ -36,15 +36,52 @@ class FileTransferChannel : public Channel
Q_DISABLE_COPY(FileTransferChannel)
public:
+ static const Feature FeatureCore;
+
static FileTransferChannelPtr create(const ConnectionPtr &connection,
const QString &objectPath, const QVariantMap &immutableProperties);
virtual ~FileTransferChannel();
+ FileTransferState state() const;
+ FileTransferStateChangeReason stateReason() const;
+
+ QString fileName() const;
+ QString contentType() const;
+ qulonglong size() const;
+
+ FileHashType contentHashType() const;
+ QString contentHash() const;
+
+ QString description() const;
+
+ QDateTime lastModificationTime() const;
+
+ qulonglong initialOffset() const;
+
+ qulonglong transferredBytes() const;
+
+ PendingOperation *provideFile(QIODevice *input);
+ PendingOperation *acceptFile(qulonglong offset, QIODevice *output);
+ void cancel();
+
+Q_SIGNALS:
+ void stateChanged(Tp::FileTransferState state,
+ Tp::FileTransferStateChangeReason reason);
+ void transferredBytesChanged(qulonglong count);
+ void initialOffsetDefined(qulonglong offset);
+
protected:
FileTransferChannel(const ConnectionPtr &connection, const QString &objectPath,
const QVariantMap &immutableProperties);
+private Q_SLOTS:
+ void gotProperties(QDBusPendingCallWatcher *watcher);
+ void onCallFinished(Tp::PendingOperation *op);
+ void onFileTrasnferStateChanged(uint state, uint stateReason);
+ void onSocketConnected();
+ void writeData();
+
private:
struct Private;
friend struct Private;
--
1.5.6.5
More information about the telepathy-commits
mailing list