/* * This file is part of TelepathyQt * * @copyright Copyright (C) 2010-2012 Collabora Ltd. * @copyright Copyright (C) 2012 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/call-content.moc.hpp" #include "TelepathyQt/_gen/cli-call-content-body.hpp" #include "TelepathyQt/_gen/cli-call-content.moc.hpp" #include #include #include #include #include #include #include namespace Tp { /* ====== CallContent ====== */ struct TP_QT_NO_EXPORT CallContent::Private { Private(CallContent *parent, const CallChannelPtr &channel); static void introspectMainProperties(Private *self); void checkIntrospectionCompleted(); CallStreamPtr addStream(const QDBusObjectPath &streamPath); CallStreamPtr lookupStream(const QDBusObjectPath &streamPath); // Public object CallContent *parent; WeakPtr channel; // Mandatory proxies Client::CallContentInterface *contentInterface; ReadinessHelper *readinessHelper; // Introspection QString name; uint type; uint disposition; CallStreams streams; CallStreams incompleteStreams; }; CallContent::Private::Private(CallContent *parent, const CallChannelPtr &channel) : parent(parent), channel(channel.data()), contentInterface(parent->interface()), readinessHelper(parent->readinessHelper()) { ReadinessHelper::Introspectables introspectables; ReadinessHelper::Introspectable introspectableCore( QSet() << 0, // makesSenseForStatuses Features(), // dependsOnFeatures QStringList(), // dependsOnInterfaces (ReadinessHelper::IntrospectFunc) &Private::introspectMainProperties, this); introspectables[FeatureCore] = introspectableCore; readinessHelper->addIntrospectables(introspectables); readinessHelper->becomeReady(FeatureCore); } void CallContent::Private::introspectMainProperties(CallContent::Private *self) { CallContent *parent = self->parent; CallChannelPtr channel = parent->channel(); parent->connect(self->contentInterface, SIGNAL(StreamsAdded(Tp::ObjectPathList)), SLOT(onStreamsAdded(Tp::ObjectPathList))); parent->connect(self->contentInterface, SIGNAL(StreamsRemoved(Tp::ObjectPathList,Tp::CallStateReason)), SLOT(onStreamsRemoved(Tp::ObjectPathList,Tp::CallStateReason))); parent->connect(self->contentInterface->requestAllProperties(), SIGNAL(finished(Tp::PendingOperation*)), SLOT(gotMainProperties(Tp::PendingOperation*))); } void CallContent::Private::checkIntrospectionCompleted() { if (!parent->isReady(FeatureCore) && incompleteStreams.size() == 0) { readinessHelper->setIntrospectCompleted(FeatureCore, true); } } CallStreamPtr CallContent::Private::addStream(const QDBusObjectPath &streamPath) { CallStreamPtr stream = CallStreamPtr( new CallStream(CallContentPtr(parent), streamPath)); incompleteStreams.append(stream); parent->connect(stream->becomeReady(), SIGNAL(finished(Tp::PendingOperation*)), SLOT(onStreamReady(Tp::PendingOperation*))); return stream; } CallStreamPtr CallContent::Private::lookupStream(const QDBusObjectPath &streamPath) { foreach (const CallStreamPtr &stream, streams) { if (stream->objectPath() == streamPath.path()) { return stream; } } foreach (const CallStreamPtr &stream, incompleteStreams) { if (stream->objectPath() == streamPath.path()) { return stream; } } return CallStreamPtr(); } /** * \class CallContent * \ingroup clientchannel * \headerfile TelepathyQt/call-content.h * * \brief The CallContent class provides an object representing a Telepathy * Call.Content. * * Instances of this class cannot be constructed directly; the only way to get * one is via CallChannel. * * See \ref async_model */ /** * Feature representing the core that needs to become ready to make the * CallContent object usable. * * Note that this feature must be enabled in order to use most CallContent * methods. See specific methods documentation for more details. * * When calling isReady(), becomeReady(), this feature is implicitly added * to the requested features. */ const Feature CallContent::FeatureCore = Feature(QLatin1String(CallContent::staticMetaObject.className()), 0); /** * Construct a new CallContent object. * * \param channel The channel owning this media content. * \param name The object path of this media content. */ CallContent::CallContent(const CallChannelPtr &channel, const QDBusObjectPath &objectPath) : StatefulDBusProxy(channel->dbusConnection(), channel->busName(), objectPath.path(), FeatureCore), OptionalInterfaceFactory(this), mPriv(new Private(this, channel)) { } /** * Class destructor. */ CallContent::~CallContent() { delete mPriv; } /** * Return the channel owning this media content. * * \return The channel owning this media content. */ CallChannelPtr CallContent::channel() const { return CallChannelPtr(mPriv->channel); } /** * Return the name of this media content. * * \return The name of this media content. */ QString CallContent::name() const { return mPriv->name; } /** * Return the type of this media content. * * \return The type of this media content. */ MediaStreamType CallContent::type() const { return (MediaStreamType) mPriv->type; } /** * Return the disposition of this media content. * * \return The disposition of this media content. */ CallContentDisposition CallContent::disposition() const { return (CallContentDisposition) mPriv->disposition; } /** * Return the media streams of this media content. * * \return A list of media streams of this media content. * \sa streamAdded(), streamRemoved() */ CallStreams CallContent::streams() const { return mPriv->streams; } /** * Removes this media content from the call. * * \return A PendingOperation which will emit PendingOperation::finished * when the call has finished. */ PendingOperation *CallContent::remove() { return new PendingVoid(mPriv->contentInterface->Remove(), CallContentPtr(this)); } /** * Return whether sending DTMF events is supported on this content. * DTMF is only supported on audio contents that implement the * #TP_QT_IFACE_CALL_CONTENT_INTERFACE_DTMF interface. * * \returns \c true if DTMF is supported, or \c false otherwise. */ bool CallContent::supportsDTMF() const { return hasInterface(TP_QT_IFACE_CALL_CONTENT_INTERFACE_DTMF); } /** * Start sending a DTMF tone on this media stream. * * Where possible, the tone will continue until stopDTMFTone() is called. * On certain protocols, it may only be possible to send events with a predetermined * length. In this case, the implementation may emit a fixed-length tone, * and the stopDTMFTone() method call should return #TP_QT_ERROR_NOT_AVAILABLE. * * If this content does not support the #TP_QT_IFACE_CALL_CONTENT_INTERFACE_DTMF * interface, the resulting PendingOperation will fail with error code * #TP_QT_ERROR_NOT_IMPLEMENTED. * * \param event A numeric event code from the #DTMFEvent enum. * \return A PendingOperation which will emit PendingOperation::finished * when the request finishes. * \sa stopDTMFTone(), supportsDTMF() */ PendingOperation *CallContent::startDTMFTone(DTMFEvent event) { if (!supportsDTMF()) { warning() << "CallContent::startDTMFTone() used with no dtmf interface"; return new PendingFailure(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("This CallContent does not support the dtmf interface"), CallContentPtr(this)); } Client::CallContentInterfaceDTMFInterface *dtmfInterface = interface(); return new PendingVoid(dtmfInterface->StartTone(event), CallContentPtr(this)); } /** * Stop sending any DTMF tone which has been started using the startDTMFTone() * method. * * If there is no current tone, the resulting PendingOperation will * finish successfully. * * If this content does not support the #TP_QT_IFACE_CALL_CONTENT_INTERFACE_DTMF * interface, the resulting PendingOperation will fail with error code * #TP_QT_ERROR_NOT_IMPLEMENTED. * * \return A PendingOperation which will emit PendingOperation::finished * when the request finishes. * \sa startDTMFTone(), supportsDTMF() */ PendingOperation *CallContent::stopDTMFTone() { if (!supportsDTMF()) { warning() << "CallContent::stopDTMFTone() used with no dtmf interface"; return new PendingFailure(TP_QT_ERROR_NOT_IMPLEMENTED, QLatin1String("This CallContent does not support the dtmf interface"), CallContentPtr(this)); } Client::CallContentInterfaceDTMFInterface *dtmfInterface = interface(); return new PendingVoid(dtmfInterface->StopTone(), CallContentPtr(this)); } void CallContent::gotMainProperties(PendingOperation *op) { if (op->isError()) { warning().nospace() << "CallContentInterface::requestAllProperties() failed with" << op->errorName() << ": " << op->errorMessage(); mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, op->errorName(), op->errorMessage()); return; } debug() << "Got reply to CallContentInterface::requestAllProperties()"; PendingVariantMap *pvm = qobject_cast(op); Q_ASSERT(pvm); QVariantMap props = pvm->result(); mPriv->name = qdbus_cast(props[QLatin1String("Name")]); mPriv->type = qdbus_cast(props[QLatin1String("Type")]); mPriv->disposition = qdbus_cast(props[QLatin1String("Disposition")]); ObjectPathList streamsPaths = qdbus_cast(props[QLatin1String("Streams")]); if (streamsPaths.size() != 0) { foreach (const QDBusObjectPath &streamPath, streamsPaths) { CallStreamPtr stream = mPriv->lookupStream(streamPath); if (!stream) { mPriv->addStream(streamPath); } } } else { mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true); } } void CallContent::onStreamsAdded(const ObjectPathList &streamsPaths) { foreach (const QDBusObjectPath &streamPath, streamsPaths) { debug() << "Received Call::Content::StreamAdded for stream" << streamPath.path(); if (mPriv->lookupStream(streamPath)) { debug() << "Stream already exists, ignoring"; return; } mPriv->addStream(streamPath); } } void CallContent::onStreamsRemoved(const ObjectPathList &streamsPaths, const CallStateReason &reason) { foreach (const QDBusObjectPath &streamPath, streamsPaths) { debug() << "Received Call::Content::StreamRemoved for stream" << streamPath.path(); CallStreamPtr stream = mPriv->lookupStream(streamPath); if (!stream) { debug() << "Stream does not exist, ignoring"; return; } bool incomplete = mPriv->incompleteStreams.contains(stream); if (incomplete) { mPriv->incompleteStreams.removeOne(stream); } else { mPriv->streams.removeOne(stream); } if (isReady(FeatureCore) && !incomplete) { emit streamRemoved(stream, reason); } mPriv->checkIntrospectionCompleted(); } } void CallContent::onStreamReady(PendingOperation *op) { PendingReady *pr = qobject_cast(op); CallStreamPtr stream = CallStreamPtr::qObjectCast(pr->proxy()); if (op->isError() || !mPriv->incompleteStreams.contains(stream)) { mPriv->incompleteStreams.removeOne(stream); mPriv->checkIntrospectionCompleted(); return; } mPriv->incompleteStreams.removeOne(stream); mPriv->streams.append(stream); if (isReady(FeatureCore)) { emit streamAdded(stream); } mPriv->checkIntrospectionCompleted(); } /** * \fn void CallContent::streamAdded(const Tp::CallStreamPtr &stream); * * This signal is emitted when a new media stream is added to this media * content. * * \param stream The media stream that was added. * \sa streams() */ /** * \fn void CallContent::streamRemoved(const Tp::CallStreamPtr &stream, const Tp::CallStateReason &reason); * * This signal is emitted when a new media stream is removed from this media * content. * * \param stream The media stream that was removed. * \param reason The reason for this removal. * \sa streams() */ /* ====== PendingCallContent ====== */ struct TP_QT_NO_EXPORT PendingCallContent::Private { Private(PendingCallContent *parent, const CallChannelPtr &channel) : parent(parent), channel(channel) { } PendingCallContent *parent; CallChannelPtr channel; CallContentPtr content; }; PendingCallContent::PendingCallContent(const CallChannelPtr &channel, const QString &name, MediaStreamType type, MediaStreamDirection direction) : PendingOperation(channel), mPriv(new Private(this, channel)) { Client::ChannelTypeCallInterface *callInterface = channel->interface(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( callInterface->AddContent(name, type, direction), this); connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotContent(QDBusPendingCallWatcher*))); } PendingCallContent::~PendingCallContent() { delete mPriv; } CallContentPtr PendingCallContent::content() const { if (!isFinished() || !isValid()) { return CallContentPtr(); } return mPriv->content; } void PendingCallContent::gotContent(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (reply.isError()) { warning().nospace() << "Call::AddContent failed with " << reply.error().name() << ": " << reply.error().message(); setFinishedWithError(reply.error()); watcher->deleteLater(); return; } QDBusObjectPath contentPath = reply.value(); CallChannelPtr channel(mPriv->channel); CallContentPtr content = channel->lookupContent(contentPath); if (!content) { content = channel->addContent(contentPath); } connect(content->becomeReady(), SIGNAL(finished(Tp::PendingOperation*)), SLOT(onContentReady(Tp::PendingOperation*))); connect(channel.data(), SIGNAL(contentRemoved(Tp::CallContentPtr,Tp::CallStateReason)), SLOT(onContentRemoved(Tp::CallContentPtr))); mPriv->content = content; watcher->deleteLater(); } void PendingCallContent::onContentReady(PendingOperation *op) { if (op->isError()) { setFinishedWithError(op->errorName(), op->errorMessage()); return; } setFinished(); } void PendingCallContent::onContentRemoved(const CallContentPtr &content) { if (isFinished()) { return; } if (mPriv->content == content) { // the content was removed before becoming ready setFinishedWithError(TP_QT_ERROR_CANCELLED, QLatin1String("Content removed before ready")); } } } // Tp