summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexandr Akulich <akulichalexander@gmail.com>2015-06-10 20:45:53 +0500
committerAlexandr Akulich <akulichalexander@gmail.com>2016-02-20 11:53:46 +0500
commit86b02c51b1d3e9c437114270a8e33e069bb0b339 (patch)
tree7cddcdb86a46c0a5efcfb9774941bc3204b6846f
parentf28d09ddbc3938cec42856aeccac1339904b479a (diff)
BaseChannelFileTransferType: Refactored with better API.
Default implementation now support IPv4 and IPv6 socket types with localhost access control. Commit-by: Alexandr Akulich <akulichalexander at gmail.com> Commit-by: Niels Ole Salscheider <niels_ole@salscheider-online.de>
-rw-r--r--TelepathyQt/base-channel-internal.h5
-rw-r--r--TelepathyQt/base-channel.cpp571
-rw-r--r--TelepathyQt/base-channel.h84
3 files changed, 534 insertions, 126 deletions
diff --git a/TelepathyQt/base-channel-internal.h b/TelepathyQt/base-channel-internal.h
index 85623964..600c52f0 100644
--- a/TelepathyQt/base-channel-internal.h
+++ b/TelepathyQt/base-channel-internal.h
@@ -171,7 +171,7 @@ class TP_QT_NO_EXPORT BaseChannelFileTransferType::Adaptee : public QObject
Q_PROPERTY(Tp::SupportedSocketMap availableSocketTypes READ availableSocketTypes)
Q_PROPERTY(qulonglong transferredBytes READ transferredBytes)
Q_PROPERTY(qulonglong initialOffset READ initialOffset)
- Q_PROPERTY(QString uri READ uri)
+ Q_PROPERTY(QString uri READ uri WRITE setUri)
Q_PROPERTY(QString fileCollection READ fileCollection)
public:
@@ -192,6 +192,9 @@ public:
QString uri() const;
QString fileCollection() const;
+public Q_SLOTS:
+ void setUri(const QString &uri);
+
private Q_SLOTS:
void acceptFile(uint addressType, uint accessControl, const QDBusVariant &accessControlParam, qulonglong offset,
const Tp::Service::ChannelTypeFileTransferAdaptor::AcceptFileContextPtr &context);
diff --git a/TelepathyQt/base-channel.cpp b/TelepathyQt/base-channel.cpp
index c13e8f04..6ea739d4 100644
--- a/TelepathyQt/base-channel.cpp
+++ b/TelepathyQt/base-channel.cpp
@@ -39,6 +39,8 @@
#include <QDateTime>
#include <QString>
+#include <QTcpServer>
+#include <QTcpSocket>
#include <QVariantMap>
namespace Tp
@@ -782,28 +784,39 @@ QString BaseChannelMessagesInterface::sendMessage(const Tp::MessagePartList &mes
// Chan.T.FileTransfer
// The BaseChannelFileTransferType code is fully or partially generated by the TelepathyQt-Generator.
struct TP_QT_NO_EXPORT BaseChannelFileTransferType::Private {
+
Private(BaseChannelFileTransferType *parent,
- const QString &contentType,
- const QString &filename,
- qulonglong size,
- uint contentHashType,
- const QString &contentHash,
- const QString &description,
- const QDateTime &date,
- const Tp::SupportedSocketMap &availableSocketTypes)
+ const QVariantMap &request)
: state(Tp::FileTransferStatePending),
- contentType(contentType),
- filename(filename),
- size(size),
- contentHashType(contentHashType),
- contentHash(contentHash),
- description(description),
- date(date),
- availableSocketTypes(availableSocketTypes),
transferredBytes(0),
initialOffset(0),
+ deviceOffset(0),
+ device(0),
+ weOpenedDevice(false),
+ serverSocket(0),
+ clientSocket(0),
adaptee(new BaseChannelFileTransferType::Adaptee(parent))
{
+ contentType = request.value(TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER + QLatin1String(".ContentType")).toString();
+ filename = request.value(TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER + QLatin1String(".Filename")).toString();
+ size = request.value(TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER + QLatin1String(".Size")).toULongLong();
+ contentHashType = request.value(TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER + QLatin1String(".ContentHashType")).toUInt();
+ contentHash = request.value(TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER + QLatin1String(".ContentHash")).toString();
+ description = request.value(TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER + QLatin1String(".Description")).toString();
+ qint64 dbusDataValue = request.value(TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER + QLatin1String(".Date")).value<qint64>();
+ if (dbusDataValue != 0) {
+ date.setTime_t(dbusDataValue);
+ }
+
+ if (request.contains(TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER + QLatin1String(".URI"))) {
+ uri = request.value(TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER + QLatin1String(".URI")).toString();
+ }
+
+ if (request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".Requested")).toBool()) {
+ direction = BaseChannelFileTransferType::Outgoing;
+ } else {
+ direction = BaseChannelFileTransferType::Incoming;
+ }
}
uint state;
@@ -814,14 +827,20 @@ struct TP_QT_NO_EXPORT BaseChannelFileTransferType::Private {
QString contentHash;
QString description;
QDateTime date;
- Tp::SupportedSocketMap availableSocketTypes;
qulonglong transferredBytes;
qulonglong initialOffset;
+ qulonglong deviceOffset;
QString uri;
QString fileCollection;
- AcceptFileCallback acceptFileCB;
- ProvideFileCallback provideFileCB;
+ QIODevice *device; // A socket to read or write file to underlying connection manager
+ bool weOpenedDevice;
+ QTcpServer *serverSocket; // Server socket is an implementation detail.
+ QIODevice *clientSocket; // A socket to communicate with a Telepathy client
+ BaseChannelFileTransferType::Direction direction;
BaseChannelFileTransferType::Adaptee *adaptee;
+
+ friend class BaseChannelFileTransferType::Adaptee;
+
};
BaseChannelFileTransferType::Adaptee::Adaptee(BaseChannelFileTransferType *interface)
@@ -899,29 +918,55 @@ QString BaseChannelFileTransferType::Adaptee::fileCollection() const
return mInterface->fileCollection();
}
+void BaseChannelFileTransferType::Adaptee::setUri(const QString &uri)
+{
+ mInterface->setUri(uri);
+}
+
void BaseChannelFileTransferType::Adaptee::acceptFile(uint addressType, uint accessControl, const QDBusVariant &accessControlParam, qulonglong offset,
const Tp::Service::ChannelTypeFileTransferAdaptor::AcceptFileContextPtr &context)
{
- qDebug() << "BaseChannelFileTransferType::Adaptee::acceptFile";
+ debug() << "BaseChannelFileTransferType::Adaptee::acceptFile";
+
+ if (mInterface->mPriv->device) {
+ context->setFinishedWithError(TP_QT_ERROR_NOT_AVAILABLE, QLatin1String("File transfer can only be started once in the same channel"));
+ return;
+ }
+
DBusError error;
- QDBusVariant address = mInterface->acceptFile(addressType, accessControl, accessControlParam, offset, &error);
+ mInterface->createSocket(addressType, accessControl, accessControlParam, &error);
+
if (error.isValid()) {
context->setFinishedWithError(error.name(), error.message());
return;
}
+
+ QDBusVariant address = mInterface->socketAddress();
+
+ mInterface->setState(Tp::FileTransferStateAccepted, Tp::FileTransferStateChangeReasonNone);
+
+ mInterface->mPriv->initialOffset = offset;
+ QMetaObject::invokeMethod(this, "initialOffsetDefined", Q_ARG(qulonglong, offset));
+
context->setFinished(address);
}
void BaseChannelFileTransferType::Adaptee::provideFile(uint addressType, uint accessControl, const QDBusVariant &accessControlParam,
const Tp::Service::ChannelTypeFileTransferAdaptor::ProvideFileContextPtr &context)
{
- qDebug() << "BaseChannelFileTransferType::Adaptee::provideFile";
+ debug() << "BaseChannelFileTransferType::Adaptee::provideFile";
+
DBusError error;
- QDBusVariant address = mInterface->provideFile(addressType, accessControl, accessControlParam, &error);
+ mInterface->createSocket(addressType, accessControl, accessControlParam, &error);
+
if (error.isValid()) {
context->setFinishedWithError(error.name(), error.message());
return;
}
+
+ QDBusVariant address = mInterface->socketAddress();
+
+ mInterface->tryToOpenAndTransfer();
context->setFinished(address);
}
@@ -930,23 +975,250 @@ void BaseChannelFileTransferType::Adaptee::provideFile(uint addressType, uint ac
* \ingroup servicecm
* \headerfile TelepathyQt/base-channel.h <TelepathyQt/BaseChannel>
*
- * \brief Base class for implementations of Channel.Type.FileTransfer
+ * \brief Base class of Channel.Type.FileTransfer channel type.
+ *
+ * Default implementation currently support only IPv4 and IPv6 sockets with localhost access control.
+ *
+ * Usage:
+ * -# Add FileTransfer to the list of the protocol requestable channel classes.
+ * -# Add FileTransfer to the list of the connection requestable channel classes.
+ * -# Setup ContactCapabilities interface and ensure that FileTransfer requestable channel class presence matches to
+ * actual local (!) and remote contacts capabilities.
+ * -# Implement initial FileTransfer channel support in createChannel callback.
+ * -# The channel of interest are those with channelType TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER.
+ * -# Create BaseChannel and plug BaseChannelFileTransferType interface.
+ * -# If transferInterface->direction() is Outgoing, notify the remote side.
+ * -# Implement incoming file request handler:
+ * -# Properly setup the request details, take care on TargetHandle and InitiatorHandle.
+ * -# Call BaseConnection::createChannel() with the details. Do not suppress handler!
+ * -# Use remoteProvideFile() to pass the input device and its offset.
+ * -# transferredBytes property will be updated automatically on bytes written to the client socket.
+ * -# Implement "remote side accepted transfer" handler:
+ * -# Use remoteAcceptFile() to pass the requested initial offset and output device.
+ * -# Update transferredBytes property on bytes written to the remote side.
+ *
+ * Incoming transfer process:
+ * -# Connection manager creates not requested channel with ChannelType = TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER and
+ * other properties, such as Filename, Size and ContentType.
+ * -# The channel initial state is Pending.
+ * -# At any time:
+ * -# Client calls AcceptFile method to configure the socket and request an initial offset. The implementation
+ * calls createSocket(), which should trigger (now or later) a call to setClientSocket() to setup the client
+ * socket. socketAddress() method used to return the socket address. This changes the state to Accepted.
+ * -# The connection manager calls remoteProvideFile() method to pass the input device and it's offset. The device
+ * offset is a number of bytes, already skipped by the device. The interface would skip remaining
+ * initialOffset - deviceOffset bytes.
+ * -# Client connects to the socket and triggers setClientSocket() call.
+ * -# The channel state is Open now.
+ * -# If the device is already ready to read, or emit readyRead() signal, the interface reads data from the device and
+ * write it to the clientSocket.
+ * -# Client socket emit bytesWritten() signal, the interface updates transferredBytes count.
+ * -# If transferredBytes == size, then the channel state changes to Completed.
+ * Otherwise the interface waits for further data from the device socket.
+ *
+ * Outgoing transfer process:
+ * -# Client requests a channel with ChannelType = TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER and other properties, such as
+ * Filename, Size and ContentType.
+ * -# Connection manager creates the requested channel with initial state Pending.
+ * -# Connection manager asks remote contact to accept the transfer.
+ * -# At any time:
+ * -# Remote contact accept file, connection manager calls remoteAcceptFile() method to pass the output device
+ * and an initial offset. This changes the state to Accepted.
+ * -# Client calls ProvideFile method to configure a socket. The implementation calls createSocket(), which should
+ * trigger (now or later) a call to setClientSocket() to setup the client socket. socketAddress() method used
+ * to return the socket address.
+ * -# Client connects to the socket and triggers setClientSocket() call.
+ * -# The channel state is Open now.
+ * -# Client writes data to the socket.
+ * -# The clientSocket emits readyRead() signal, the interface reads the data from the clientSocket and write it to the
+ * io device.
+ * -# Connection manager calls updates transferredBytes property on actual data write.
+ * -# If transferredBytes == size, then the channel state changes to Completed.
+ * Otherwise the interface waits for further data from the client socket.
+ *
+ * Subclassing:
+ * + Reimplement a public virtual method availableSocketTypes() to expose extra socket types.
+ * + Overload protected createSocket() method to provide own socket address type, access control and its param
+ * implementation.
+ * + Custom createSocket() implementation MUST be paired with custom socketAddress() method implementation.
+ * + Use setClientSocket() method to pass the client socket.
+ *
*/
/**
* Class constructor.
*/
-BaseChannelFileTransferType::BaseChannelFileTransferType(const QString &contentType,
- const QString &filename,
- qulonglong size,
- uint contentHashType,
- const QString &contentHash,
- const QString &description,
- const QDateTime &date,
- const Tp::SupportedSocketMap &availableSocketTypes)
+BaseChannelFileTransferType::BaseChannelFileTransferType(const QVariantMap &request)
: AbstractChannelInterface(TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER),
- mPriv(new Private(this, contentType, filename, size, contentHashType, contentHash, description, date, availableSocketTypes))
+ mPriv(new Private(this, request))
+{
+}
+
+bool BaseChannelFileTransferType::createSocket(uint addressType, uint accessControl, const QDBusVariant &accessControlParam, Tp::DBusError *error)
+{
+ Q_UNUSED(accessControlParam);
+
+ if (accessControl != Tp::SocketAccessControlLocalhost) {
+ error->set(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Requested access control mechanism is not supported."));
+ return false;
+ }
+
+ QHostAddress address;
+
+ switch (addressType) {
+ case Tp::SocketAddressTypeIPv4:
+ address = QHostAddress(QHostAddress::LocalHost);
+ break;
+ case Tp::SocketAddressTypeIPv6:
+ address = QHostAddress(QHostAddress::LocalHostIPv6);
+ break;
+ default:
+ error->set(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Requested address type is not supported."));
+ return false;
+ }
+
+ if (mPriv->serverSocket) {
+ error->set(TP_QT_ERROR_NOT_AVAILABLE, QLatin1String("File transfer can only be started once in the same channel"));
+ return false;
+ }
+
+ mPriv->serverSocket = new QTcpServer(this);
+ mPriv->serverSocket->setMaxPendingConnections(1);
+
+ connect(mPriv->serverSocket, SIGNAL(newConnection()), this, SLOT(onSocketConnection()));
+
+ bool result = mPriv->serverSocket->listen(address);
+ if (!result) {
+ error->set(TP_QT_ERROR_NETWORK_ERROR, mPriv->serverSocket->errorString());
+ }
+
+ return result;
+}
+
+QDBusVariant BaseChannelFileTransferType::socketAddress() const
+{
+ if (!mPriv->serverSocket) {
+ return QDBusVariant();
+ }
+
+ switch (mPriv->serverSocket->serverAddress().protocol()) {
+ case QAbstractSocket::IPv4Protocol: {
+ SocketAddressIPv4 a;
+ a.address = mPriv->serverSocket->serverAddress().toString();
+ a.port = mPriv->serverSocket->serverPort();
+ return QDBusVariant(QVariant::fromValue(a));
+ }
+ case QAbstractSocket::IPv6Protocol: {
+ SocketAddressIPv6 a;
+ a.address = mPriv->serverSocket->serverAddress().toString();
+ a.port = mPriv->serverSocket->serverPort();
+ return QDBusVariant(QVariant::fromValue(a));
+ }
+ default:
+ break;
+ }
+
+ return QDBusVariant();
+}
+
+void BaseChannelFileTransferType::setTransferredBytes(qulonglong count)
+{
+ if (mPriv->transferredBytes == count) {
+ return;
+ }
+
+ mPriv->transferredBytes = count;
+ QMetaObject::invokeMethod(mPriv->adaptee, "transferredBytesChanged", Q_ARG(qulonglong, count)); //Can simply use emit in Qt5
+
+ if (transferredBytes() == size()) {
+ mPriv->clientSocket->close();
+ mPriv->serverSocket->close();
+ setState(Tp::FileTransferStateCompleted, Tp::FileTransferStateChangeReasonNone);
+ }
+}
+
+void BaseChannelFileTransferType::setClientSocket(QIODevice *socket)
+{
+ mPriv->clientSocket = socket;
+
+ if (!socket) {
+ warning() << "BaseChannelFileTransferType::setClientSocket() called with a null socket.";
+ return;
+ }
+
+ switch (mPriv->direction) {
+ case BaseChannelFileTransferType::Outgoing:
+ connect(mPriv->clientSocket, SIGNAL(readyRead()), this, SLOT(doTransfer()));
+ break;
+ case BaseChannelFileTransferType::Incoming:
+ connect(mPriv->clientSocket, SIGNAL(bytesWritten(qint64)), this, SLOT(onBytesWritten(qint64)));
+ break;
+ default:
+ // Should not be ever possible
+ Q_ASSERT(0);
+ break;
+ }
+
+ tryToOpenAndTransfer();
+}
+
+void BaseChannelFileTransferType::onSocketConnection()
+{
+ setClientSocket(mPriv->serverSocket->nextPendingConnection());
+}
+
+void BaseChannelFileTransferType::doTransfer()
+{
+ if (!mPriv->clientSocket || !mPriv->device) {
+ return;
+ }
+
+ QIODevice *input = 0;
+ QIODevice *output = 0;
+
+ switch (mPriv->direction) {
+ case BaseChannelFileTransferType::Outgoing:
+ input = mPriv->clientSocket;
+ output = mPriv->device;
+ break;
+ case BaseChannelFileTransferType::Incoming:
+ input = mPriv->device;
+ output = mPriv->clientSocket;
+ break;
+ default:
+ // Should not be ever possible
+ Q_ASSERT(0);
+ break;
+ }
+
+ static const int c_blockSize = 16 * 1024;
+ char buffer[c_blockSize];
+ char *inputPointer = buffer;
+
+ qint64 length = input->read(buffer, sizeof(buffer));
+
+ if (length) {
+ // deviceOffset is the number of already skipped bytes
+ if (mPriv->deviceOffset + length > initialOffset()) {
+ if (mPriv->deviceOffset < initialOffset()) {
+ qint64 diff = initialOffset() - mPriv->deviceOffset;
+ length -= diff;
+ inputPointer += diff;
+ mPriv->deviceOffset += diff;
+ }
+ output->write(inputPointer, length);
+ }
+ mPriv->deviceOffset += length;
+ }
+
+ if (input->bytesAvailable() > 0) {
+ QMetaObject::invokeMethod(this, "doTransfer", Qt::QueuedConnection);
+ }
+}
+
+void BaseChannelFileTransferType::onBytesWritten(qint64 count)
{
+ setTransferredBytes(transferredBytes() + count);
}
/**
@@ -984,9 +1256,19 @@ QVariantMap BaseChannelFileTransferType::immutableProperties() const
QVariant::fromValue(date().toTime_t()));
map.insert(TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER + QLatin1String(".AvailableSocketTypes"),
QVariant::fromValue(availableSocketTypes()));
+
+ if (mPriv->direction == Outgoing) {
+ map.insert(TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER + QLatin1String(".URI"), QVariant::fromValue(uri()));
+ }
+
return map;
}
+BaseChannelFileTransferType::Direction BaseChannelFileTransferType::direction() const
+{
+ return mPriv->direction;
+}
+
uint BaseChannelFileTransferType::state() const
{
return mPriv->state;
@@ -994,8 +1276,13 @@ uint BaseChannelFileTransferType::state() const
void BaseChannelFileTransferType::setState(uint state, uint reason)
{
+ if (mPriv->state == state) {
+ return;
+ }
+
mPriv->state = state;
QMetaObject::invokeMethod(mPriv->adaptee, "fileTransferStateChanged", Q_ARG(uint, state), Q_ARG(uint, reason)); //Can simply use emit in Qt5
+ emit stateChanged(state, reason);
}
QString BaseChannelFileTransferType::contentType() const
@@ -1035,7 +1322,10 @@ QDateTime BaseChannelFileTransferType::date() const
Tp::SupportedSocketMap BaseChannelFileTransferType::availableSocketTypes() const
{
- return mPriv->availableSocketTypes;
+ Tp::SupportedSocketMap types;
+ types.insert(Tp::SocketAddressTypeIPv4, Tp::UIntList() << Tp::SocketAccessControlLocalhost);
+
+ return types;
}
qulonglong BaseChannelFileTransferType::transferredBytes() const
@@ -1043,22 +1333,11 @@ qulonglong BaseChannelFileTransferType::transferredBytes() const
return mPriv->transferredBytes;
}
-void BaseChannelFileTransferType::setTransferredBytes(qulonglong count)
-{
- mPriv->transferredBytes = count;
- QMetaObject::invokeMethod(mPriv->adaptee, "transferredBytesChanged", Q_ARG(qulonglong, count)); //Can simply use emit in Qt5
-}
-
qulonglong BaseChannelFileTransferType::initialOffset() const
{
return mPriv->initialOffset;
}
-void BaseChannelFileTransferType::setInitialOffset(qulonglong initialOffset)
-{
- mPriv->initialOffset = initialOffset;
-}
-
QString BaseChannelFileTransferType::uri() const
{
return mPriv->uri;
@@ -1066,9 +1345,21 @@ QString BaseChannelFileTransferType::uri() const
void BaseChannelFileTransferType::setUri(const QString &uri)
{
+ if (mPriv->direction == Outgoing) {
+ warning() << "BaseChannelFileTransferType::setUri(): Failed to set URI property for outgoing transfer.";
+ return;
+ }
+
+ // The property can be written only before AcceptFile.
+ if (state() != FileTransferStatePending) {
+ warning() << "BaseChannelFileTransferType::setUri(): Failed to set URI property after AcceptFile call.";
+ return;
+ }
+
mPriv->uri = uri;
+ QMetaObject::invokeMethod(mPriv->adaptee, "uriDefined", Q_ARG(QString, uri)); //Can simply use emit in Qt5
+ emit uriDefined(uri);
}
-
QString BaseChannelFileTransferType::fileCollection() const
{
return mPriv->fileCollection;
@@ -1085,47 +1376,183 @@ void BaseChannelFileTransferType::createAdaptor()
mPriv->adaptee, dbusObject());
}
-void BaseChannelFileTransferType::setAcceptFileCallback(const AcceptFileCallback &cb)
+bool BaseChannelFileTransferType::remoteAcceptFile(QIODevice *output, qulonglong offset)
{
- mPriv->acceptFileCB = cb;
-}
+ QString errorText;
+ bool deviceIsAlreadynOpened = output && output->isOpen();
-QDBusVariant BaseChannelFileTransferType::acceptFile(uint addressType, uint accessControl, const QDBusVariant &accessControlParam, qulonglong offset, DBusError *error)
-{
- if (!mPriv->acceptFileCB.isValid()) {
- error->set(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented"));
- return QDBusVariant();
+ if (!output) {
+ errorText = QLatin1String("The device must not be null.");
+ goto errorLabel;
}
- return mPriv->acceptFileCB(addressType, accessControl, accessControlParam, offset, error);
-}
-void BaseChannelFileTransferType::setProvideFileCallback(const ProvideFileCallback &cb)
-{
- mPriv->provideFileCB = cb;
-}
+ if (mPriv->state != Tp::FileTransferStatePending) {
+ errorText = QLatin1String("The state should be Pending.");
+ goto errorLabel;
+ }
-QDBusVariant BaseChannelFileTransferType::provideFile(uint addressType, uint accessControl, const QDBusVariant &accessControlParam, DBusError *error)
-{
- if (!mPriv->provideFileCB.isValid()) {
- error->set(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("Not implemented"));
- return QDBusVariant();
+ if (mPriv->direction != Outgoing) {
+ errorText = QLatin1String("The direction should be Outgoing.");
+ goto errorLabel;
+ }
+
+ if (offset > size()) {
+ errorText = QLatin1String("The offset should be less than the size.");
+ goto errorLabel;
+ }
+
+ if (mPriv->device) {
+ errorText = QLatin1String("The device is already set.");
+ goto errorLabel;
+ }
+
+ if (!deviceIsAlreadynOpened) {
+ if (!output->open(QIODevice::WriteOnly)) {
+ errorText = QLatin1String("Unable to open the device .");
+ goto errorLabel;
+ }
+
+ if (!output->isSequential()) {
+ if (!output->seek(offset)) {
+ errorText = QLatin1String("Unable to seek the device to the offset.");
+ goto errorLabel;
+ }
+ }
}
- return mPriv->provideFileCB(addressType, accessControl, accessControlParam, error);
+
+ if (!output->isWritable()) {
+ errorText = QLatin1String("The device is not writable.");
+ goto errorLabel;
+ }
+
+ if (!errorText.isEmpty()) {
+ errorLabel:
+ warning() << "BaseChannelFileTransferType::remoteAcceptFile(): Invalid call:" << errorText;
+ setState(Tp::FileTransferStateCancelled, Tp::FileTransferStateChangeReasonLocalError);
+
+ return false;
+ }
+
+ mPriv->device = output;
+ mPriv->deviceOffset = offset;
+ mPriv->weOpenedDevice = !deviceIsAlreadynOpened;
+ mPriv->initialOffset = offset;
+
+ QMetaObject::invokeMethod(mPriv->adaptee, "initialOffsetDefined", Q_ARG(qulonglong, offset)); //Can simply use emit in Qt5
+ setState(Tp::FileTransferStateAccepted, Tp::FileTransferStateChangeReasonNone);
+
+ return true;
}
-void BaseChannelFileTransferType::fileTransferStateChanged(uint state, uint reason)
+/*!
+ *
+ * Connection manager should call this method to pass the input device and its offset.
+ * The interface would skip remaining initialOffset - deviceOffset bytes.
+ *
+ * \param input The input device
+ * \param deviceOffset The number of bytes, already skipped by the device.
+ *
+ * \return True if success, false otherwise.
+ */
+bool BaseChannelFileTransferType::remoteProvideFile(QIODevice *input, qulonglong deviceOffset)
{
- QMetaObject::invokeMethod(mPriv->adaptee, "fileTransferStateChanged", Q_ARG(uint, state), Q_ARG(uint, reason)); //Can simply use emit in Qt5
+ QString errorText;
+ bool deviceIsAlreadyOpened = input && input->isOpen();
+
+ if (!input) {
+ errorText = QLatin1String("The device must not be null.");
+ goto errorLabel;
+ }
+
+ switch (mPriv->state) {
+ case Tp::FileTransferStatePending:
+ case Tp::FileTransferStateAccepted:
+ break;
+ default:
+ errorText = QLatin1String("The state should be Pending or Accepted.");
+ goto errorLabel;
+ break;
+ }
+
+ if (mPriv->direction != Incoming) {
+ errorText = QLatin1String("The direction should be Incoming.");
+ goto errorLabel;
+ }
+
+ if (deviceOffset > initialOffset()) {
+ errorText = QLatin1String("The deviceOffset should be less or equal to the initialOffset.");
+ goto errorLabel;
+ }
+
+ if (mPriv->device) {
+ errorText = QLatin1String("The device is already set.");
+ goto errorLabel;
+ }
+
+ if (!deviceIsAlreadyOpened) {
+ if (!input->open(QIODevice::ReadOnly)) {
+ errorText = QLatin1String("Unable to open the device .");
+ goto errorLabel;
+ }
+
+ if (!input->isSequential()) {
+ if (!input->seek(initialOffset())) {
+ errorText = QLatin1String("Unable to seek the device to the initial offset.");
+ goto errorLabel;
+ }
+ deviceOffset = initialOffset();
+ }
+ }
+
+ if (!input->isReadable()) {
+ errorText = QLatin1String("The device is not readable.");
+ goto errorLabel;
+ }
+
+ if (!errorText.isEmpty()) {
+ errorLabel:
+ warning() << "BaseChannelFileTransferType::remoteProvideFile(): Invalid call:" << errorText;
+ setState(Tp::FileTransferStateCancelled, Tp::FileTransferStateChangeReasonLocalError);
+
+ return false;
+ }
+
+ mPriv->deviceOffset = deviceOffset;
+
+ mPriv->device = input;
+ mPriv->weOpenedDevice = !deviceIsAlreadyOpened;
+
+ connect(mPriv->device, SIGNAL(readyRead()), this, SLOT(doTransfer()));
+
+ tryToOpenAndTransfer();
+
+ return true;
}
-void BaseChannelFileTransferType::initialOffsetDefined(qulonglong initialOffset)
+void BaseChannelFileTransferType::tryToOpenAndTransfer()
{
- QMetaObject::invokeMethod(mPriv->adaptee, "initialOffsetDefined", Q_ARG(qulonglong, initialOffset)); //Can simply use emit in Qt5
+ if (state() == Tp::FileTransferStateAccepted) {
+ setState(Tp::FileTransferStateOpen, Tp::FileTransferStateChangeReasonNone);
+ setTransferredBytes(initialOffset());
+ }
+
+ if (state() == Tp::FileTransferStateOpen) {
+ if (mPriv->clientSocket && mPriv->device) {
+ QMetaObject::invokeMethod(this, "doTransfer", Qt::QueuedConnection);
+
+ }
+ }
}
-void BaseChannelFileTransferType::uriDefined(const QString &uri)
+void BaseChannelFileTransferType::close()
{
- QMetaObject::invokeMethod(mPriv->adaptee, "uriDefined", Q_ARG(QString, uri)); //Can simply use emit in Qt5
+ uint transferState = state();
+ if (transferState == FileTransferStatePending ||
+ transferState == FileTransferStateAccepted ||
+ transferState == FileTransferStateOpen) {
+ // The file transfer was cancelled
+ setState(Tp::FileTransferStateCancelled, Tp::FileTransferStateChangeReasonLocalStopped);
+ }
}
// Chan.T.RoomList
diff --git a/TelepathyQt/base-channel.h b/TelepathyQt/base-channel.h
index 856ac09e..23fab8c3 100644
--- a/TelepathyQt/base-channel.h
+++ b/TelepathyQt/base-channel.h
@@ -228,48 +228,26 @@ class TP_QT_EXPORT BaseChannelFileTransferType : public AbstractChannelInterface
Q_DISABLE_COPY(BaseChannelFileTransferType)
public:
- static BaseChannelFileTransferTypePtr create(const QString &contentType,
- const QString &filename,
- qulonglong size,
- uint contentHashType,
- const QString &contentHash,
- const QString &description,
- const QDateTime &date,
- const Tp::SupportedSocketMap &availableSocketTypes)
+ enum Direction {
+ Incoming,
+ Outgoing
+ };
+
+ static BaseChannelFileTransferTypePtr create(const QVariantMap &request)
{
- return BaseChannelFileTransferTypePtr(new BaseChannelFileTransferType(contentType,
- filename,
- size,
- contentHashType,
- contentHash,
- description,
- date,
- availableSocketTypes));
+ return BaseChannelFileTransferTypePtr(new BaseChannelFileTransferType(request));
}
template<typename BaseChannelFileTransferTypeSubclass>
- static SharedPtr<BaseChannelFileTransferTypeSubclass> create(const QString &contentType,
- const QString &filename,
- qulonglong size,
- uint contentHashType,
- const QString &contentHash,
- const QString &description,
- const QDateTime &date,
- const Tp::SupportedSocketMap &availableSocketTypes)
+ static SharedPtr<BaseChannelFileTransferTypeSubclass> create(const QVariantMap &request)
{
return SharedPtr<BaseChannelFileTransferTypeSubclass>(
- new BaseChannelFileTransferTypeSubclass(contentType,
- filename,
- size,
- contentHashType,
- contentHash,
- description,
- date,
- availableSocketTypes));
+ new BaseChannelFileTransferTypeSubclass(request));
}
virtual ~BaseChannelFileTransferType();
QVariantMap immutableProperties() const;
+ Direction direction() const;
QString contentType() const;
QString filename() const;
@@ -278,46 +256,46 @@ public:
QString contentHash() const;
QString description() const;
QDateTime date() const;
- Tp::SupportedSocketMap availableSocketTypes() const;
+ virtual Tp::SupportedSocketMap availableSocketTypes() const;
uint state() const;
void setState(uint state, uint reason);
qulonglong transferredBytes() const;
void setTransferredBytes(qulonglong count);
-
qulonglong initialOffset() const;
- void setInitialOffset(qulonglong initialOffset);
QString uri() const;
- void setUri(const QString &uri);
QString fileCollection() const;
void setFileCollection(const QString &fileCollection);
- typedef Callback5<QDBusVariant, uint, uint, const QDBusVariant &, qulonglong, DBusError*> AcceptFileCallback;
- void setAcceptFileCallback(const AcceptFileCallback &cb);
- QDBusVariant acceptFile(uint addressType, uint accessControl, const QDBusVariant &accessControlParam, qulonglong offset, DBusError *error);
-
- typedef Callback4<QDBusVariant, uint, uint, const QDBusVariant &, DBusError*> ProvideFileCallback;
- void setProvideFileCallback(const ProvideFileCallback &cb);
- QDBusVariant provideFile(uint addressType, uint accessControl, const QDBusVariant &accessControlParam, DBusError *error);
+ bool remoteAcceptFile(QIODevice *output, qulonglong offset);
+ bool remoteProvideFile(QIODevice *input, qulonglong deviceOffset = 0);
- void fileTransferStateChanged(uint state, uint reason);
- void initialOffsetDefined(qulonglong initialOffset);
+Q_SIGNALS:
+ void stateChanged(uint state, uint reason);
void uriDefined(const QString &uri);
protected:
- BaseChannelFileTransferType(const QString &contentType,
- const QString &filename,
- qulonglong size,
- uint contentHashType,
- const QString &contentHash,
- const QString &description,
- const QDateTime &date,
- const Tp::SupportedSocketMap &availableSocketTypes);
+ BaseChannelFileTransferType(const QVariantMap &request);
+
+ virtual bool createSocket(uint addressType, uint accessControl, const QDBusVariant &accessControlParam, DBusError *error);
+ virtual QDBusVariant socketAddress() const;
+
+ void setClientSocket(QIODevice *socket);
+
+ void close(); // Add Q_DECL_OVERRIDE in Qt5
+
+private Q_SLOTS:
+ TP_QT_NO_EXPORT void onSocketConnection();
+ TP_QT_NO_EXPORT void doTransfer();
+ TP_QT_NO_EXPORT void onBytesWritten(qint64 count);
private:
+ TP_QT_NO_EXPORT void setUri(const QString &uri);
+ TP_QT_NO_EXPORT void tryToOpenAndTransfer();
+
void createAdaptor();
class Adaptee;