diff options
Diffstat (limited to 'qt4/TelepathyQt4/outgoing-file-transfer-channel.cpp')
-rw-r--r-- | qt4/TelepathyQt4/outgoing-file-transfer-channel.cpp | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/qt4/TelepathyQt4/outgoing-file-transfer-channel.cpp b/qt4/TelepathyQt4/outgoing-file-transfer-channel.cpp new file mode 100644 index 000000000..5abf48141 --- /dev/null +++ b/qt4/TelepathyQt4/outgoing-file-transfer-channel.cpp @@ -0,0 +1,371 @@ +/** + * 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/OutgoingFileTransferChannel> + +#include "TelepathyQt4/_gen/outgoing-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 +{ + +static const int FT_BLOCK_SIZE = 16 * 1024; + +struct TELEPATHY_QT4_NO_EXPORT OutgoingFileTransferChannel::Private +{ + Private(OutgoingFileTransferChannel *parent); + ~Private(); + + // Public object + OutgoingFileTransferChannel *parent; + + Client::ChannelTypeFileTransferInterface *fileTransferInterface; + + // Introspection + QIODevice *input; + QTcpSocket *socket; + SocketAddressIPv4 addr; + + qint64 pos; +}; + +OutgoingFileTransferChannel::Private::Private(OutgoingFileTransferChannel *parent) + : parent(parent), + fileTransferInterface(parent->interface<Client::ChannelTypeFileTransferInterface>()), + input(0), + socket(0), + pos(0) +{ +} + +OutgoingFileTransferChannel::Private::~Private() +{ +} + +/** + * \class OutgoingFileTransferChannel + * \ingroup clientchannel + * \headerfile TelepathyQt4/outgoing-file-transfer-channel.h <TelepathyQt4/OutgoingFileTransferChannel> + * + * \brief The OutgoingFileTransferChannel class represents a Telepathy channel + * of type FileTransfer for outgoing 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 + * OutgoingFileTransferChannel 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 OutgoingFileTransferChannel::FeatureCore = + Feature(QLatin1String(FileTransferChannel::staticMetaObject.className()), 0); // FT::FeatureCore + +/** + * Create a new OutgoingFileTransferChannel object. + * + * \param connection Connection owning this channel, and specifying the + * service. + * \param objectPath The channel object path. + * \param immutableProperties The channel immutable properties. + * \return An OutgoingFileTransferChannelPtr object pointing to the newly created + * OutgoingFileTransferChannel object. + */ +OutgoingFileTransferChannelPtr OutgoingFileTransferChannel::create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties) +{ + return OutgoingFileTransferChannelPtr(new OutgoingFileTransferChannel( + connection, objectPath, immutableProperties, + OutgoingFileTransferChannel::FeatureCore)); +} + +/** + * Construct a new OutgoingFileTransferChannel 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 OutgoingFileTransferChannel::FeatureCore. + */ +OutgoingFileTransferChannel::OutgoingFileTransferChannel( + const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature) + : FileTransferChannel(connection, objectPath, immutableProperties, coreFeature), + mPriv(new Private(this)) +{ +} + +/** + * Class destructor. + */ +OutgoingFileTransferChannel::~OutgoingFileTransferChannel() +{ + delete mPriv; +} + +/** + * Provide the file for an outgoing file transfer which has been offered. + * + * The state will change to #FileTransferStateOpen as soon as the transfer + * starts. + * The given input device should not be destroyed until the state() + * changes to #FileTransferStateCompleted or #FileTransferStateCancelled. + * If input is a sequential device QIODevice::isSequential(), it should be + * closed when no more data is available, so that it's known when to stop reading. + * + * Only the primary handler of a file transfer channel may call this method. + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \param input A QIODevice object where the data will be read from. + * \return A PendingOperation object which will emit PendingOperation::finished + * when the call has finished. + * \sa stateChanged(), state(), stateReason() + */ +PendingOperation *OutgoingFileTransferChannel::provideFile(QIODevice *input) +{ + if (!isReady(FileTransferChannel::FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling provideFile"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + OutgoingFileTransferChannelPtr(this)); + } + + // 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(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("File transfer can only be started once in the same channel"), + OutgoingFileTransferChannelPtr(this)); + } + + if ((!input->isOpen() && !input->open(QIODevice::ReadOnly)) && + !input->isReadable()) { + warning() << "Unable to open IO device for reading"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_PERMISSION_DENIED), + QLatin1String("Unable to open IO device for reading"), + OutgoingFileTransferChannelPtr(this)); + } + + mPriv->input = input; + connect(input, + SIGNAL(aboutToClose()), + SLOT(onInputAboutToClose())); + + PendingVariant *pv = new PendingVariant( + mPriv->fileTransferInterface->ProvideFile( + SocketAddressTypeIPv4, + SocketAccessControlLocalhost, + QDBusVariant(QVariant(QString()))), + OutgoingFileTransferChannelPtr(this)); + connect(pv, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onProvideFileFinished(Tp::PendingOperation*))); + return pv; +} + +void OutgoingFileTransferChannel::onProvideFileFinished(PendingOperation *op) +{ + if (op->isError()) { + warning() << "Error providing 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) { + connectToHost(); + } +} + +void OutgoingFileTransferChannel::connectToHost() +{ + if (isConnected() || mPriv->addr.address.isNull()) { + return; + } + + 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(bytesWritten(qint64)), + SLOT(doTransfer())); + + debug().nospace() << "Connecting to host " << + mPriv->addr.address << ":" << mPriv->addr.port << "..."; + mPriv->socket->connectToHost(mPriv->addr.address, mPriv->addr.port); +} + +void OutgoingFileTransferChannel::onSocketConnected() +{ + debug() << "Connected to host"; + setConnected(); + + connect(mPriv->input, SIGNAL(readyRead()), + SLOT(doTransfer())); + + // for non sequential devices, let's seek to the initialOffset + if (!mPriv->input->isSequential()) { + if (mPriv->input->seek(initialOffset())) { + mPriv->pos = initialOffset(); + } + } + + debug() << "Starting transfer..."; + doTransfer(); +} + +void OutgoingFileTransferChannel::onSocketDisconnected() +{ + debug() << "Disconnected from host"; + setFinished(); +} + +void OutgoingFileTransferChannel::onSocketError(QAbstractSocket::SocketError error) +{ + debug() << "Socket error" << error; + setFinished(); +} + +void OutgoingFileTransferChannel::onInputAboutToClose() +{ + debug() << "Input closed"; + + // read all remaining data from input device and write to output device + if (isConnected()) { + QByteArray data; + data = mPriv->input->readAll(); + mPriv->socket->write(data); // never fails + } + + setFinished(); +} + +void OutgoingFileTransferChannel::doTransfer() +{ + // read FT_BLOCK_SIZE each time, as input can be a QFile, we don't want to + // block reading the whole file + char buffer[FT_BLOCK_SIZE]; + char *p = buffer; + + memset(buffer, 0, sizeof(buffer)); + qint64 len = mPriv->input->read(buffer, sizeof(buffer)); + + bool scheduleTransfer = false; + if (((qulonglong) mPriv->pos < initialOffset()) && (len > 0)) { + qint64 skip = (qint64) qMin(initialOffset() - mPriv->pos, + (qulonglong) len); + + debug() << "skipping" << skip << "bytes"; + if (skip == len) { + // nothing to write, all data read was skipped + // schedule a transfer, as readyRead may never be called and + // bytesWriiten will not + scheduleTransfer = true; + goto end; + } + + p += skip; + len -= skip; + } + + if (len > 0) { + mPriv->socket->write(p, len); // never fails + } + +end: + if (len == -1 || (!mPriv->input->isSequential() && mPriv->input->atEnd())) { + // error or EOF + setFinished(); + return; + } + + mPriv->pos += len; + + if (scheduleTransfer) { + QMetaObject::invokeMethod(this, SLOT(doTransfer()), + Qt::QueuedConnection); + } +} + +void OutgoingFileTransferChannel::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(bytesWritten(qint64)), + this, SLOT(doTransfer())); + mPriv->socket->close(); + } + + if (mPriv->input) { + disconnect(mPriv->input, SIGNAL(aboutToClose()), + this, SLOT(onInputAboutToClose())); + disconnect(mPriv->input, SIGNAL(readyRead()), + this, SLOT(doTransfer())); + mPriv->input->close(); + } + + FileTransferChannel::setFinished(); +} + +} // Tp |