/*
* 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-stream.moc.hpp"
#include "TelepathyQt/_gen/cli-call-stream-body.hpp"
#include "TelepathyQt/_gen/cli-call-stream.moc.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace Tp
{
struct TP_QT_NO_EXPORT CallStream::Private
{
Private(CallStream *parent, const CallContentPtr &content);
static void introspectMainProperties(Private *self);
void processRemoteMembersChanged();
struct RemoteMembersChangedInfo;
// Public object
CallStream *parent;
WeakPtr content;
// Mandatory proxies
Client::CallStreamInterface *streamInterface;
ReadinessHelper *readinessHelper;
// Introspection
uint localSendingState;
ContactSendingStateMap remoteMembers;
QHash remoteMembersContacts;
bool canRequestReceiving;
QQueue< QSharedPointer > remoteMembersChangedQueue;
QSharedPointer currentRemoteMembersChangedInfo;
};
struct TP_QT_NO_EXPORT CallStream::Private::RemoteMembersChangedInfo
{
RemoteMembersChangedInfo(const ContactSendingStateMap &updates,
const HandleIdentifierMap &identifiers,
const UIntList &removed,
const CallStateReason &reason)
: updates(updates),
identifiers(identifiers),
removed(removed),
reason(reason)
{
}
static QSharedPointer create(
const ContactSendingStateMap &updates,
const HandleIdentifierMap &identifiers,
const UIntList &removed,
const CallStateReason &reason)
{
RemoteMembersChangedInfo *info = new RemoteMembersChangedInfo(
updates, identifiers, removed, reason);
return QSharedPointer(info);
}
ContactSendingStateMap updates;
HandleIdentifierMap identifiers;
UIntList removed;
CallStateReason reason;
};
CallStream::Private::Private(CallStream *parent, const CallContentPtr &content)
: parent(parent),
content(content.data()),
streamInterface(parent->interface()),
readinessHelper(parent->readinessHelper()),
localSendingState(SendingStateNone),
canRequestReceiving(true)
{
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 CallStream::Private::introspectMainProperties(CallStream::Private *self)
{
CallStream *parent = self->parent;
parent->connect(self->streamInterface,
SIGNAL(LocalSendingStateChanged(uint,Tp::CallStateReason)),
SLOT(onLocalSendingStateChanged(uint,Tp::CallStateReason)));
parent->connect(self->streamInterface,
SIGNAL(RemoteMembersChanged(Tp::ContactSendingStateMap,Tp::HandleIdentifierMap,Tp::UIntList,Tp::CallStateReason)),
SLOT(onRemoteMembersChanged(Tp::ContactSendingStateMap,Tp::HandleIdentifierMap,Tp::UIntList,Tp::CallStateReason)));
parent->connect(self->streamInterface->requestAllProperties(),
SIGNAL(finished(Tp::PendingOperation*)),
SLOT(gotMainProperties(Tp::PendingOperation*)));
}
void CallStream::Private::processRemoteMembersChanged()
{
if (currentRemoteMembersChangedInfo) { // currently building contacts
return;
}
if (remoteMembersChangedQueue.isEmpty()) {
if (!parent->isReady(FeatureCore)) {
readinessHelper->setIntrospectCompleted(FeatureCore, true);
}
return;
}
currentRemoteMembersChangedInfo = remoteMembersChangedQueue.dequeue();
QSet pendingRemoteMembers;
for (ContactSendingStateMap::const_iterator i = currentRemoteMembersChangedInfo->updates.constBegin();
i != currentRemoteMembersChangedInfo->updates.constEnd(); ++i) {
pendingRemoteMembers.insert(i.key());
}
foreach(uint i, currentRemoteMembersChangedInfo->removed) {
pendingRemoteMembers.insert(i);
}
if (!pendingRemoteMembers.isEmpty()) {
ConnectionPtr connection = parent->content()->channel()->connection();
connection->lowlevel()->injectContactIds(currentRemoteMembersChangedInfo->identifiers);
ContactManagerPtr contactManager = connection->contactManager();
PendingContacts *contacts = contactManager->contactsForHandles(
pendingRemoteMembers.toList());
parent->connect(contacts,
SIGNAL(finished(Tp::PendingOperation*)),
SLOT(gotRemoteMembersContacts(Tp::PendingOperation*)));
} else {
currentRemoteMembersChangedInfo.clear();
processRemoteMembersChanged();
}
}
/**
* \class CallStream
* \ingroup clientchannel
* \headerfile TelepathyQt/call-stream.h
*
* \brief The CallStream class provides an object representing a Telepathy
* Call.Stream.
*
* Instances of this class cannot be constructed directly; the only way to get
* one is via CallContent.
*
* See \ref async_model
*/
/**
* Feature representing the core that needs to become ready to make the
* CallStream object usable.
*
* Note that this feature must be enabled in order to use most CallStream
* methods. See specific methods documentation for more details.
*
* When calling isReady(), becomeReady(), this feature is implicitly added
* to the requested features.
*/
const Feature CallStream::FeatureCore = Feature(QLatin1String(CallStream::staticMetaObject.className()), 0);
/**
* Construct a new CallStream object.
*
* \param content The content owning this call stream.
* \param objectPath The object path of this call stream.
*/
CallStream::CallStream(const CallContentPtr &content, const QDBusObjectPath &objectPath)
: StatefulDBusProxy(content->dbusConnection(), content->busName(),
objectPath.path(), FeatureCore),
OptionalInterfaceFactory(this),
mPriv(new Private(this, content))
{
}
/**
* Class destructor.
*/
CallStream::~CallStream()
{
delete mPriv;
}
/**
* Return the content owning this call stream.
*
* \return The content owning this call stream.
*/
CallContentPtr CallStream::content() const
{
return CallContentPtr(mPriv->content);
}
/**
* Returns whether the user can request that a remote contact starts
* sending on this stream. Not all protocols allow the user to ask
* the other side to start sending media.
*
* \return true if the user can request that a remote contact starts
* sending on this stream, or false otherwise.
* \sa requestReceiving()
*/
bool CallStream::canRequestReceiving() const
{
return mPriv->canRequestReceiving;
}
/**
* Return the contacts whose the call stream is with.
*
* \return The contacts whose the call stream is with.
* \sa remoteMembersRemoved()
*/
Contacts CallStream::remoteMembers() const
{
return mPriv->remoteMembersContacts.values().toSet();
}
/**
* Return the call stream local sending state.
*
* \return The call stream local sending state.
* \sa localSendingStateChanged()
*/
SendingState CallStream::localSendingState() const
{
return (SendingState) mPriv->localSendingState;
}
/**
* Return the call stream remote sending state for a given \a contact.
*
* \return The call stream remote sending state for a contact.
* \sa remoteSendingStateChanged()
*/
SendingState CallStream::remoteSendingState(const ContactPtr &contact) const
{
if (!contact) {
return SendingStateNone;
}
for (ContactSendingStateMap::const_iterator i = mPriv->remoteMembers.constBegin();
i != mPriv->remoteMembers.constEnd(); ++i) {
uint handle = i.key();
SendingState sendingState = (SendingState) i.value();
if (handle == contact->handle()[0]) {
return sendingState;
}
}
return SendingStateNone;
}
/**
* Request that media starts or stops being sent on this call stream.
*
* \return A PendingOperation which will emit PendingOperation::finished
* when the call has finished.
* \sa localSendingStateChanged()
*/
PendingOperation *CallStream::requestSending(bool send)
{
return new PendingVoid(mPriv->streamInterface->SetSending(send), CallStreamPtr(this));
}
/**
* Request that a remote \a contact stops or starts sending on this call stream.
*
* \return A PendingOperation which will emit PendingOperation::finished
* when the call has finished.
* \sa remoteSendingStateChanged()
*/
PendingOperation *CallStream::requestReceiving(const ContactPtr &contact, bool receive)
{
if (!contact) {
return new PendingFailure(TP_QT_ERROR_INVALID_ARGUMENT,
QLatin1String("Invalid contact"), CallStreamPtr(this));
} else if (!mPriv->canRequestReceiving && receive) {
return new PendingFailure(TP_QT_ERROR_NOT_IMPLEMENTED,
QLatin1String("Requesting the other side to start sending media "
"is not allowed by this protocol"),
CallStreamPtr(this));
}
return new PendingVoid(mPriv->streamInterface->RequestReceiving(contact->handle()[0], receive),
CallStreamPtr(this));
}
void CallStream::gotMainProperties(PendingOperation *op)
{
if (op->isError()) {
warning().nospace() << "CallStreamInterface::requestAllProperties() failed with " <<
op->errorName() << ": " << op->errorMessage();
mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false,
op->errorName(), op->errorMessage());
return;
}
debug() << "Got reply to CallStreamInterface::requestAllProperties()";
PendingVariantMap *pvm = qobject_cast(op);
Q_ASSERT(pvm);
QVariantMap props = pvm->result();
mPriv->canRequestReceiving = qdbus_cast(props[QLatin1String("CanRequestReceiving")]);
mPriv->localSendingState = qdbus_cast(props[QLatin1String("LocalSendingState")]);
ContactSendingStateMap remoteMembers =
qdbus_cast(props[QLatin1String("RemoteMembers")]);
HandleIdentifierMap remoteMemberIdentifiers =
qdbus_cast(props[QLatin1String("RemoteMemberIdentifiers")]);
mPriv->remoteMembersChangedQueue.enqueue(Private::RemoteMembersChangedInfo::create(
remoteMembers, remoteMemberIdentifiers, UIntList(), CallStateReason()));
mPriv->processRemoteMembersChanged();
}
void CallStream::gotRemoteMembersContacts(PendingOperation *op)
{
PendingContacts *pc = qobject_cast(op);
if (!pc->isValid()) {
warning().nospace() << "Getting contacts failed with " <<
pc->errorName() << ":" << pc->errorMessage() << ", ignoring";
mPriv->currentRemoteMembersChangedInfo.clear();
mPriv->processRemoteMembersChanged();
return;
}
QMap removed;
for (ContactSendingStateMap::const_iterator i =
mPriv->currentRemoteMembersChangedInfo->updates.constBegin();
i != mPriv->currentRemoteMembersChangedInfo->updates.constEnd(); ++i) {
mPriv->remoteMembers.insert(i.key(), i.value());
}
foreach (const ContactPtr &contact, pc->contacts()) {
mPriv->remoteMembersContacts.insert(contact->handle()[0], contact);
}
foreach (uint handle, mPriv->currentRemoteMembersChangedInfo->removed) {
mPriv->remoteMembers.remove(handle);
if (isReady(FeatureCore) && mPriv->remoteMembersContacts.contains(handle)) {
removed.insert(handle, mPriv->remoteMembersContacts[handle]);
// make sure we don't have updates for removed contacts
mPriv->currentRemoteMembersChangedInfo->updates.remove(handle);
}
mPriv->remoteMembersContacts.remove(handle);
}
foreach (uint handle, pc->invalidHandles()) {
mPriv->remoteMembers.remove(handle);
if (isReady(FeatureCore) && mPriv->remoteMembersContacts.contains(handle)) {
removed.insert(handle, mPriv->remoteMembersContacts[handle]);
// make sure we don't have updates for invalid handles
mPriv->currentRemoteMembersChangedInfo->updates.remove(handle);
}
mPriv->remoteMembersContacts.remove(handle);
}
if (isReady(FeatureCore)) {
CallChannelPtr channel(content()->channel());
QHash remoteSendingStates;
for (ContactSendingStateMap::const_iterator i =
mPriv->currentRemoteMembersChangedInfo->updates.constBegin();
i != mPriv->currentRemoteMembersChangedInfo->updates.constEnd(); ++i) {
uint handle = i.key();
SendingState sendingState = (SendingState) i.value();
Q_ASSERT(mPriv->remoteMembersContacts.contains(handle));
remoteSendingStates.insert(mPriv->remoteMembersContacts[handle], sendingState);
mPriv->remoteMembers.insert(i.key(), i.value());
}
if (!remoteSendingStates.isEmpty()) {
emit remoteSendingStateChanged(remoteSendingStates,
mPriv->currentRemoteMembersChangedInfo->reason);
}
if (!removed.isEmpty()) {
emit remoteMembersRemoved(removed.values().toSet(),
mPriv->currentRemoteMembersChangedInfo->reason);
}
}
mPriv->currentRemoteMembersChangedInfo.clear();
mPriv->processRemoteMembersChanged();
}
void CallStream::onLocalSendingStateChanged(uint state, const CallStateReason &reason)
{
mPriv->localSendingState = state;
emit localSendingStateChanged((SendingState) state, reason);
}
void CallStream::onRemoteMembersChanged(const ContactSendingStateMap &updates,
const HandleIdentifierMap &identifiers,
const UIntList &removed,
const CallStateReason &reason)
{
if (updates.isEmpty() && removed.isEmpty()) {
debug() << "Received Call::Stream::RemoteMembersChanged with 0 removals and "
"updates, skipping it";
return;
}
debug() << "Received Call::Stream::RemoteMembersChanged with" << updates.size() <<
"updated and" << removed.size() << "removed";
mPriv->remoteMembersChangedQueue.enqueue(
Private::RemoteMembersChangedInfo::create(updates, identifiers, removed, reason));
mPriv->processRemoteMembersChanged();
}
/**
* \fn void CallStream::localSendingStateChanged(Tp::SendingState localSendingState, const Tp::CallStateReason &reason);
*
* This signal is emitted when the local sending state of this call stream
* changes.
*
* \param localSendingState The new local sending state of this call stream.
* \param reason The reason that caused this change
* \sa localSendingState()
*/
/**
* \fn void CallStream::remoteSendingStateChanged(const QHash &remoteSendingStates, const Tp::CallStateReason &reason);
*
* This signal is emitted when any remote sending state of this call stream
* changes.
*
* \param remoteSendingStates The new remote sending states of this call stream.
* \param reason The reason that caused these changes
* \sa remoteSendingState()
*/
/**
* \fn void CallStream::remoteMembersRemoved(const Tp::Contacts &members, const Tp::CallStateReason &reason);
*
* This signal is emitted when one or more members of this stream are removed.
*
* \param members The members that were removed from this call stream.
* \param reason The reason for that caused these removals
* \sa remoteMembers()
*/
} // Tp