diff options
Diffstat (limited to 'qt4/TelepathyQt4/incoming-file-transfer-channel.cpp')
-rw-r--r-- | qt4/TelepathyQt4/incoming-file-transfer-channel.cpp | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/qt4/TelepathyQt4/incoming-file-transfer-channel.cpp b/qt4/TelepathyQt4/incoming-file-transfer-channel.cpp new file mode 100644 index 000000000..a9bdad520 --- /dev/null +++ b/qt4/TelepathyQt4/incoming-file-transfer-channel.cpp @@ -0,0 +1,390 @@ +/** + * This file is part of TelepathyQt4 + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt4/IncomingFileTransferChannel> + +#include "TelepathyQt4/_gen/incoming-file-transfer-channel.moc.hpp" + +#include "TelepathyQt4/debug-internal.h" + +#include <TelepathyQt4/Connection> +#include <TelepathyQt4/PendingFailure> +#include <TelepathyQt4/PendingVariant> +#include <TelepathyQt4/Types> +#include <TelepathyQt4/types-internal.h> + +#include <QIODevice> +#include <QTcpSocket> + +namespace Tp +{ + +struct TELEPATHY_QT4_NO_EXPORT IncomingFileTransferChannel::Private +{ + Private(IncomingFileTransferChannel *parent); + ~Private(); + + // Public object + IncomingFileTransferChannel *parent; + + Client::ChannelTypeFileTransferInterface *fileTransferInterface; + + QIODevice *output; + QTcpSocket *socket; + SocketAddressIPv4 addr; + + qulonglong requestedOffset; + qint64 pos; +}; + +IncomingFileTransferChannel::Private::Private(IncomingFileTransferChannel *parent) + : parent(parent), + fileTransferInterface(parent->interface<Client::ChannelTypeFileTransferInterface>()), + output(0), + socket(0), + requestedOffset(0), + pos(0) +{ + parent->connect(fileTransferInterface, + SIGNAL(URIDefined(QString)), + SLOT(onUriDefined(QString))); + parent->connect(fileTransferInterface, + SIGNAL(URIDefined(QString)), + SIGNAL(uriDefined(QString))); +} + +IncomingFileTransferChannel::Private::~Private() +{ +} + +/** + * \class IncomingFileTransferChannel + * \ingroup clientchannel + * \headerfile TelepathyQt4/incoming-file-transfer-channel.h <TelepathyQt4/IncomingFileTransferChannel> + * + * \brief The IncomingFileTransferChannel class represents a Telepathy channel + * of type FileTransfer for incoming file transfers. + * + * For more details, please refer to \telepathy_spec. + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Feature representing the core that needs to become ready to make the + * IncomingFileTransferChannel object usable. + * + * This is currently the same as FileTransferChannel::FeatureCore, but may change to include more. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature IncomingFileTransferChannel::FeatureCore = + Feature(QLatin1String(FileTransferChannel::staticMetaObject.className()), 0); // FT::FeatureCore + +/** + * Create a new IncomingFileTransferChannel object. + * + * \param connection Connection owning this channel, and specifying the + * service. + * \param objectPath The channel object path. + * \param immutableProperties The channel immutable properties. + * \return A IncomingFileTransferChannelPtr object pointing to the newly created + * IncomingFileTransfer object. + */ +IncomingFileTransferChannelPtr IncomingFileTransferChannel::create( + const ConnectionPtr &connection, const QString &objectPath, + const QVariantMap &immutableProperties) +{ + return IncomingFileTransferChannelPtr(new IncomingFileTransferChannel( + connection, objectPath, immutableProperties, + IncomingFileTransferChannel::FeatureCore)); +} + +/** + * Construct a new IncomingFileTransferChannel object. + * + * \param connection Connection owning this channel, and specifying the + * service. + * \param objectPath The channel object path. + * \param immutableProperties The channel immutable properties. + * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should + * depend on IncomingFileTransferChannel::FeatureCore. + */ +IncomingFileTransferChannel::IncomingFileTransferChannel( + const ConnectionPtr &connection, const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature) + : FileTransferChannel(connection, objectPath, immutableProperties, coreFeature), + mPriv(new Private(this)) +{ +} + +/** + * Class destructor. + */ +IncomingFileTransferChannel::~IncomingFileTransferChannel() +{ + delete mPriv; +} + +/** + * Set the URI where the file will be saved. + * + * This property may be set by the channel handler before calling AcceptFile to inform observers + * where the incoming file will be saved. When the URI property is set, the signal + * uriDefined() is emitted. + * + * This method requires IncomingFileTransferChannel::FeatureCore to be ready. + * + * \param uri The URI where the file will be saved. + * \return A PendingOperation object which will emit PendingOperation::finished + * when the call has finished. + * \sa FileTransferChannel::uri(), uriDefined() + */ +PendingOperation *IncomingFileTransferChannel::setUri(const QString& uri) +{ + if (!isReady(FileTransferChannel::FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling setUri"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + IncomingFileTransferChannelPtr(this)); + } + + if (state() != FileTransferStatePending) { + warning() << "setUri must be called before calling acceptFile"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Cannot set URI after calling acceptFile"), + IncomingFileTransferChannelPtr(this)); + } + + return mPriv->fileTransferInterface->setPropertyURI(uri); +} + +/** + * Accept a file transfer that's in the #FileTransferStatePending state(). + * + * The state will change to #FileTransferStateOpen as soon as the transfer + * starts. + * The given output device should not be closed/destroyed until the state() + * changes to #FileTransferStateCompleted or #FileTransferStateCancelled. + * + * Only the primary handler of a file transfer channel may call this method. + * + * This method requires IncomingFileTransferChannel::FeatureCore to be ready. + * + * \param offset The desired offset in bytes where the file transfer should + * start. The offset is taken from the beginning of the file. + * Specifying an offset of zero will start the transfer from the + * beginning of the file. The offset that is actually given in the + * initialOffset() method can differ from this argument where the + * requested offset is not supported. (For example, some + * protocols do not support offsets at all so the initialOffset() + * will always be 0.). + * \param output A QIODevice object where the data will be written to. The + * device should be ready to use when the state() changes to + * #FileTransferStateCompleted. + * If the transfer is cancelled, state() becomes + * #FileTransferStateCancelled, the data in \a output should be + * ignored + * \return A PendingOperation object which will emit PendingOperation::finished + * when the call has finished. + * \sa FileTransferChannel::stateChanged(), FileTransferChannel::state(), + * FileTransferChannel::stateReason(), FileTransferChannel::initialOffset() + */ +PendingOperation *IncomingFileTransferChannel::acceptFile(qulonglong offset, + QIODevice *output) +{ + if (!isReady(FileTransferChannel::FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling acceptFile"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + IncomingFileTransferChannelPtr(this)); + } + + // 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(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("File transfer can only be started once in the same channel"), + IncomingFileTransferChannelPtr(this)); + } + + if ((!output->isOpen() && !output->open(QIODevice::WriteOnly)) && + (!output->isWritable())) { + warning() << "Unable to open IO device for writing"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_PERMISSION_DENIED), + QLatin1String("Unable to open IO device for writing"), + IncomingFileTransferChannelPtr(this)); + } + + mPriv->output = output; + + mPriv->requestedOffset = offset; + + PendingVariant *pv = new PendingVariant( + mPriv->fileTransferInterface->AcceptFile(SocketAddressTypeIPv4, + SocketAccessControlLocalhost, QDBusVariant(QVariant(QString())), + offset), + IncomingFileTransferChannelPtr(this)); + connect(pv, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onAcceptFileFinished(Tp::PendingOperation*))); + return pv; +} + +void IncomingFileTransferChannel::onAcceptFileFinished(PendingOperation *op) +{ + if (op->isError()) { + warning() << "Error accepting file transfer " << + op->errorName() << ":" << op->errorMessage(); + invalidate(op->errorName(), op->errorMessage()); + 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 (state() == FileTransferStateOpen) { + // now we have the address and we are already opened, + // connect to host + connectToHost(); + } +} + +void IncomingFileTransferChannel::connectToHost() +{ + if (isConnected() || mPriv->addr.address.isNull()) { + return; + } + + // we already have initialOffsetDefined, called before State became Open, so + // let's make sure everything is ok. + if (initialOffset() > mPriv->requestedOffset) { + // either the CM or the sender is doing something really wrong here, + // cancel the transfer. + warning() << "InitialOffset bigger than requested offset, " + "cancelling the transfer"; + cancel(); + invalidate(QLatin1String(TELEPATHY_QT4_ERROR_INCONSISTENT), + QLatin1String("Initial offset bigger than requested offset")); + return; + } + + mPriv->pos = initialOffset(); + + mPriv->socket = new QTcpSocket(this); + + connect(mPriv->socket, SIGNAL(connected()), + SLOT(onSocketConnected())); + connect(mPriv->socket, SIGNAL(disconnected()), + SLOT(onSocketDisconnected())); + connect(mPriv->socket, SIGNAL(error(QAbstractSocket::SocketError)), + SLOT(onSocketError(QAbstractSocket::SocketError))); + connect(mPriv->socket, SIGNAL(readyRead()), + SLOT(doTransfer())); + + debug().nospace() << "Connecting to host " << + mPriv->addr.address << ":" << mPriv->addr.port << "..."; + mPriv->socket->connectToHost(mPriv->addr.address, mPriv->addr.port); +} + +void IncomingFileTransferChannel::onSocketConnected() +{ + debug() << "Connected to host"; + setConnected(); + + doTransfer(); +} + +void IncomingFileTransferChannel::onSocketDisconnected() +{ + debug() << "Disconnected from host"; + setFinished(); +} + +void IncomingFileTransferChannel::onSocketError(QAbstractSocket::SocketError error) +{ + setFinished(); +} + +void IncomingFileTransferChannel::doTransfer() +{ + QByteArray data; + while (mPriv->socket->bytesAvailable()) { + data = mPriv->socket->readAll(); + + // skip until we reach requetedOffset and start writing from there + if ((qulonglong) mPriv->pos < mPriv->requestedOffset) { + if ((qulonglong) data.length() <= (mPriv->requestedOffset - mPriv->pos)) { + break; + } + data = data.mid(mPriv->requestedOffset - mPriv->pos); + } + + mPriv->output->write(data); // never fails + } + + mPriv->pos += data.length(); +} + +void IncomingFileTransferChannel::setFinished() +{ + if (isFinished()) { + // it shouldn't happen but let's make sure + return; + } + + if (mPriv->socket) { + disconnect(mPriv->socket, SIGNAL(connected()), + this, SLOT(onSocketConnected())); + disconnect(mPriv->socket, SIGNAL(disconnected()), + this, SLOT(onSocketDisconnected())); + disconnect(mPriv->socket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(onSocketError(QAbstractSocket::SocketError))); + disconnect(mPriv->socket, SIGNAL(readyRead()), + this, SLOT(doTransfer())); + mPriv->socket->close(); + } + + if (mPriv->output) { + mPriv->output->close(); + } + + FileTransferChannel::setFinished(); +} + +/** + * \fn void IncomingFileTransferChannel::uriDefined(const QString &uri) + * + * Emitted when the value of uri() changes. + * + * \param uri The new URI of this file transfer channel. + * \sa FileTransferChannel::uri(), setUri() + */ + +} // Tp |