/* * This file is part of TelepathyQt4 * * Copyright (C) 2009 Collabora Ltd. * Copyright (C) 2009 Nokia Corporation * * 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 #include "TelepathyQt4/_gen/incoming-file-transfer-channel.moc.hpp" #include "TelepathyQt4/debug-internal.h" #include #include #include #include #include #include #include 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()), output(0), socket(0), requestedOffset(0), pos(0) { } IncomingFileTransferChannel::Private::~Private() { } /** * \class IncomingFileTransferChannel * \ingroup clientchannel * \headerfile TelepathyQt4/incoming-file-transfer-channel.h * * \brief The IncomingFileTransferChannel class provides an object representing * a Telepathy channel of type FileTransfer for incoming file transfers. */ /** * 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); /** * Create a new InconmingFileTransferChannel object. * * \param connection Connection owning this channel, and specifying the * service. * \param objectPath The object path of this channel. * \param immutableProperties The immutable properties of this channel. * \return A StreamedMediaChannelPtr object pointing to the newly created * StreamedMediaChannel object. */ IncomingFileTransferChannelPtr IncomingFileTransferChannel::create( const ConnectionPtr &connection, const QString &objectPath, const QVariantMap &immutableProperties) { return IncomingFileTransferChannelPtr(new IncomingFileTransferChannel( connection, objectPath, immutableProperties)); } /** * Construct a new IncomingFileTransfer channel. * * \param connection Connection owning this channel, and specifying the * service. * \param objectPath The object path of this channel. * \param immutableProperties The immutable properties of this channel. * \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; } /** * 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 FileTransferChannel::FeatureCore to be enabled. * * \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 stateChanged(), state(), stateReason(), 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(op); mPriv->addr = qdbus_cast(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(); } } // Tp