/** * This file is part of TelepathyQt * * @copyright Copyright (C) 2008-2010 Collabora Ltd. * @copyright Copyright (C) 2008-2010 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 #include "TelepathyQt/_gen/pending-channel.moc.hpp" #include "TelepathyQt/debug-internal.h" #include "TelepathyQt/fake-handler-manager-internal.h" #include "TelepathyQt/request-temporary-handler-internal.h" #include #include #include #include #include #include #include #include #include namespace Tp { struct TP_QT_NO_EXPORT PendingChannel::Private { class FakeAccountFactory; ConnectionPtr connection; bool create; bool yours; QString channelType; uint handleType; uint handle; QVariantMap immutableProperties; ChannelPtr channel; ClientRegistrarPtr cr; SharedPtr handler; HandledChannelNotifier *notifier; static uint numHandlers; }; uint PendingChannel::Private::numHandlers = 0; class TP_QT_NO_EXPORT PendingChannel::Private::FakeAccountFactory : public AccountFactory { public: static AccountFactoryPtr create(const AccountPtr &account) { return AccountFactoryPtr(new FakeAccountFactory(account)); } ~FakeAccountFactory() override { } AccountPtr account() const { return mAccount; } protected: AccountPtr construct(const QString &busName, const QString &objectPath, const ConnectionFactoryConstPtr &connFactory, const ChannelFactoryConstPtr &chanFactory, const ContactFactoryConstPtr &contactFactory) const override { if (mAccount->objectPath() != objectPath) { warning() << "Account received by the fake factory is different from original account"; } return mAccount; } private: FakeAccountFactory(const AccountPtr &account) : AccountFactory(account->dbusConnection(), Features()), mAccount(account) { } AccountPtr mAccount; }; /** * \class PendingChannel * \ingroup clientchannel * \headerfile TelepathyQt/pending-channel.h * * \brief The PendingChannel class represents the parameters of and the reply to * an asynchronous channel request. * * Instances of this class cannot be constructed directly; the only way to get * one is trough Connection or Account. * * See \ref async_model */ /** * Construct a new PendingChannel object that will fail immediately. * * \param connection Connection to use. * \param errorName The error name. * \param errorMessage The error message. */ PendingChannel::PendingChannel(const ConnectionPtr &connection, const QString &errorName, const QString &errorMessage) : PendingOperation(connection), mPriv(new Private) { mPriv->connection = connection; mPriv->yours = false; mPriv->handleType = 0; mPriv->handle = 0; mPriv->notifier = nullptr; mPriv->create = false; setFinishedWithError(errorName, errorMessage); } /** * Construct a new PendingChannel object. * * \param connection Connection to use. * \param request A dictionary containing the desirable properties. * \param create Whether createChannel or ensureChannel should be called. */ PendingChannel::PendingChannel(const ConnectionPtr &connection, const QVariantMap &request, bool create, int timeout) : PendingOperation(connection), mPriv(new Private) { mPriv->connection = connection; mPriv->yours = create; mPriv->channelType = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType")).toString(); mPriv->handleType = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType")).toUInt(); mPriv->handle = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle")).toUInt(); mPriv->notifier = nullptr; mPriv->create = create; Client::ConnectionInterfaceRequestsInterface *requestsInterface = connection->interface(); if (create) { QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( requestsInterface->CreateChannel(request, timeout), this); connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onConnectionCreateChannelFinished(QDBusPendingCallWatcher*))); } else { QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( requestsInterface->EnsureChannel(request, timeout), this); connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onConnectionEnsureChannelFinished(QDBusPendingCallWatcher*))); } } PendingChannel::PendingChannel(const AccountPtr &account, const QVariantMap &request, const QDateTime &userActionTime, bool create) : PendingOperation(account), mPriv(new Private) { mPriv->yours = true; mPriv->channelType = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType")).toString(); mPriv->handleType = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType")).toUInt(); mPriv->handle = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle")).toUInt(); mPriv->cr = ClientRegistrar::create( Private::FakeAccountFactory::create(account), account->connectionFactory(), account->channelFactory(), account->contactFactory()); mPriv->handler = RequestTemporaryHandler::create(account); mPriv->notifier = nullptr; mPriv->create = create; QString handlerName = QString(QLatin1String("TpQtRaH_%1_%2")) .arg(account->dbusConnection().baseService() .replace(QLatin1String(":"), QLatin1String("_")) .replace(QLatin1String("."), QLatin1String("_"))) .arg(Private::numHandlers++); if (!mPriv->cr->registerClient(mPriv->handler, handlerName, false)) { warning() << "Unable to register handler" << handlerName; setFinishedWithError(TP_QT_ERROR_NOT_AVAILABLE, QLatin1String("Unable to register handler")); return; } connect(mPriv->handler.data(), SIGNAL(error(QString,QString)), SLOT(onHandlerError(QString,QString))); connect(mPriv->handler.data(), SIGNAL(channelReceived(Tp::ChannelPtr,QDateTime,Tp::ChannelRequestHints)), SLOT(onHandlerChannelReceived(Tp::ChannelPtr))); handlerName = QString(QLatin1String("org.freedesktop.Telepathy.Client.%1")).arg(handlerName); debug() << "Requesting channel through account using handler" << handlerName; PendingChannelRequest *pcr; if (create) { pcr = account->createChannel(request, userActionTime, handlerName, ChannelRequestHints()); } else { pcr = account->ensureChannel(request, userActionTime, handlerName, ChannelRequestHints()); } connect(pcr, SIGNAL(finished(Tp::PendingOperation*)), SLOT(onAccountCreateChannelFinished(Tp::PendingOperation*))); } /** * Construct a new PendingChannel object that will fail immediately. * * \param errorName The name of a D-Bus error. * \param errorMessage The error message. */ PendingChannel::PendingChannel(const QString &errorName, const QString &errorMessage) : PendingOperation(ConnectionPtr()), mPriv(new Private) { setFinishedWithError(errorName, errorMessage); } /** * Class destructor. */ PendingChannel::~PendingChannel() { delete mPriv; } /** * Return the connection through which the channel request was made. * * Note that if this channel request was created through Account, a null ConnectionPtr will be * returned. * * \return A pointer to the Connection object. */ ConnectionPtr PendingChannel::connection() const { return mPriv->connection; } /** * Return whether this channel belongs to this process. * * If \c false, the caller must assume that some other process is * handling this channel; if \c true, the caller should handle it * themselves or delegate it to another client. * * \return \c true if it belongs, \c false otherwise. */ bool PendingChannel::yours() const { if (!isFinished()) { warning() << "PendingChannel::yours called before finished, returning undefined value"; } else if (!isValid()) { warning() << "PendingChannel::yours called when not valid, returning undefined value"; } return mPriv->yours; } /** * Return the channel type specified in the channel request. * * \return The D-Bus interface name for the type of the channel. */ const QString &PendingChannel::channelType() const { return mPriv->channelType; } /** * If the channel request has finished, return the handle type of the resulting * channel. Otherwise, return the handle type that was requested. * * (One example of a request producing a different target handle type is that * on protocols like MSN, one-to-one conversations don't really exist, and if * you request a text channel with handle type HandleTypeContact, what you * will actually get is a text channel with handle type HandleTypeNone, with * the requested contact as a member.) * * \return The target handle type as #HandleType. * \sa targetHandle() */ uint PendingChannel::targetHandleType() const { return mPriv->handleType; } /** * If the channel request has finished, return the target handle of the * resulting channel. Otherwise, return the target handle that was requested * (which might be different in some situations - see targetHandleType()). * * \return An integer representing the target handle, which is of the type * targetHandleType() indicates. * \sa targetHandleType() */ uint PendingChannel::targetHandle() const { return mPriv->handle; } /** * If this channel request has finished, return the immutable properties of * the resulting channel. Otherwise, return an empty map. * * The keys and values in this map are defined by the \telepathy_spec, * or by third-party extensions to that specification. * These are the properties that cannot change over the lifetime of the * channel; they're announced in the result of the request, for efficiency. * This map should be passed to the constructor of Channel or its subclasses * (such as TextChannel). * * These properties can also be used to process channels in a way that does * not require the creation of a Channel object - for instance, a * ChannelDispatcher implementation should be able to classify and process * channels based on their immutable properties, without needing to create * Channel objects. * * \return The immutable properties as QVariantMap. */ QVariantMap PendingChannel::immutableProperties() const { QVariantMap props = mPriv->immutableProperties; // This is a reasonable guess - if it's Yours it's guaranteedly Requested by us, and if it's not // it could be either Requested by somebody else but also an incoming channel just as well. if (!props.contains(TP_QT_IFACE_CHANNEL + QLatin1String(".Requested"))) { debug() << "CM didn't provide Requested in channel immutable props, guessing" << mPriv->yours; props[TP_QT_IFACE_CHANNEL + QLatin1String(".Requested")] = mPriv->yours; } // Also, the spec says that if the channel was Requested by the local user, InitiatorHandle must // be the Connection's self handle if (!props.contains(TP_QT_IFACE_CHANNEL + QLatin1String(".InitiatorHandle"))) { if (qdbus_cast(props.value(TP_QT_IFACE_CHANNEL + QLatin1String(".Requested")))) { if (connection() && connection()->isReady(Connection::FeatureCore)) { debug() << "CM didn't provide InitiatorHandle in channel immutable props, but we " "know it's the conn's self handle (and have it)"; props[TP_QT_IFACE_CHANNEL + QLatin1String(".InitiatorHandle")] = connection()->selfHandle(); } } } return props; } /** * Return the channel resulting from the channel request. * * \return A pointer to the Channel object. */ ChannelPtr PendingChannel::channel() const { if (!isFinished()) { warning() << "PendingChannel::channel called before finished, returning 0"; return ChannelPtr(); } else if (!isValid()) { warning() << "PendingChannel::channel called when not valid, returning 0"; return ChannelPtr(); } return mPriv->channel; } /** * If this channel request has finished and was created through Account, * return a HandledChannelNotifier object that will keep track of channel() being re-requested. * * \return A HandledChannelNotifier instance, or 0 if an error occurred. */ HandledChannelNotifier *PendingChannel::handledChannelNotifier() const { if (!isFinished()) { warning() << "PendingChannel::handledChannelNotifier called before finished, returning 0"; return nullptr; } else if (!isValid()) { warning() << "PendingChannel::handledChannelNotifier called when not valid, returning 0"; return nullptr; } if (mPriv->cr && !mPriv->notifier) { mPriv->notifier = new HandledChannelNotifier(mPriv->cr, mPriv->handler); } return mPriv->notifier; } void PendingChannel::onConnectionCreateChannelFinished(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (!reply.isError()) { QString objectPath = reply.argumentAt<0>().path(); QVariantMap map = reply.argumentAt<1>(); debug() << "Got reply to Connection.CreateChannel - object path:" << objectPath; PendingReady *channelReady = connection()->channelFactory()->proxy(connection(), objectPath, map); mPriv->channel = ChannelPtr::qObjectCast(channelReady->proxy()); mPriv->immutableProperties = map; mPriv->channelType = map.value(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType")).toString(); mPriv->handleType = map.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType")).toUInt(); mPriv->handle = map.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle")).toUInt(); connect(channelReady, SIGNAL(finished(Tp::PendingOperation*)), SLOT(onChannelReady(Tp::PendingOperation*))); } else { debug().nospace() << "CreateChannel failed:" << reply.error().name() << ": " << reply.error().message(); setFinishedWithError(reply.error()); } watcher->deleteLater(); } void PendingChannel::onConnectionEnsureChannelFinished(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (!reply.isError()) { mPriv->yours = reply.argumentAt<0>(); QString objectPath = reply.argumentAt<1>().path(); QVariantMap map = reply.argumentAt<2>(); debug() << "Got reply to Connection.EnsureChannel - object path:" << objectPath; PendingReady *channelReady = connection()->channelFactory()->proxy(connection(), objectPath, map); mPriv->channel = ChannelPtr::qObjectCast(channelReady->proxy()); mPriv->immutableProperties = map; mPriv->channelType = map.value(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType")).toString(); mPriv->handleType = map.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType")).toUInt(); mPriv->handle = map.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle")).toUInt(); connect(channelReady, SIGNAL(finished(Tp::PendingOperation*)), SLOT(onChannelReady(Tp::PendingOperation*))); } else { debug().nospace() << "EnsureChannel failed:" << reply.error().name() << ": " << reply.error().message(); setFinishedWithError(reply.error()); } watcher->deleteLater(); } void PendingChannel::onChannelReady(PendingOperation *op) { if (!op->isError()) { setFinished(); } else { debug() << "Making the channel ready for" << this << "failed with" << op->errorName() << ":" << op->errorMessage(); setFinishedWithError(op->errorName(), op->errorMessage()); } } void PendingChannel::onHandlerError(const QString &errorName, const QString &errorMessage) { if (isFinished()) { return; } warning() << "Creating/ensuring channel failed with" << errorName << ":" << errorMessage; setFinishedWithError(errorName, errorMessage); } void PendingChannel::onHandlerChannelReceived(const ChannelPtr &channel) { if (isFinished()) { warning() << "Handler received the channel but this operation already finished due " "to failure in the channel request"; return; } mPriv->handleType = channel->targetHandleType(); mPriv->handle = channel->targetHandle(); mPriv->immutableProperties = channel->immutableProperties(); mPriv->channel = channel; // register the CR in FakeHandlerManager so that at least one handler per bus stays alive // until all channels requested using R&H gets invalidated/destroyed. // This is important in case Mission Control happens to restart while // the channel is still in use, since it will close each channel it // doesn't find a handler for it. FakeHandlerManager::instance()->registerClientRegistrar(mPriv->cr); setFinished(); } void PendingChannel::onAccountCreateChannelFinished(PendingOperation *op) { if (isFinished()) { if (isError()) { warning() << "Creating/ensuring channel finished with a failure after the internal " "handler already got a channel, ignoring"; } return; } if (op->isError()) { warning() << "Creating/ensuring channel failed with" << op->errorName() << ":" << op->errorMessage(); setFinishedWithError(op->errorName(), op->errorMessage()); return; } if (!mPriv->handler->isDBusHandlerInvoked()) { // Our handler hasn't be called but the channel request is complete. // That means another handler handled the channels so we don't own it. if (mPriv->create) { warning() << "Creating/ensuring channel failed with" << TP_QT_ERROR_SERVICE_CONFUSED << ":" << QLatin1String("CD.CreateChannel/WithHints returned successfully and " "the handler didn't receive the channel yet"); setFinishedWithError(TP_QT_ERROR_SERVICE_CONFUSED, QLatin1String("CD.CreateChannel/WithHints returned successfully and " "the handler didn't receive the channel yet")); } else { warning() << "Creating/ensuring channel failed with" << TP_QT_ERROR_NOT_YOURS << ":" << QLatin1String("Another handler is handling this channel"); setFinishedWithError(TP_QT_ERROR_NOT_YOURS, QLatin1String("Another handler is handling this channel")); } return; } } } // Tp