/**
* This file is part of TelepathyQt
*
* @copyright Copyright (C) 2009 Collabora Ltd.
* @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
#include "TelepathyQt/channel-dispatch-operation-internal.h"
#include "TelepathyQt/_gen/cli-channel-dispatch-operation-body.hpp"
#include "TelepathyQt/_gen/cli-channel-dispatch-operation.moc.hpp"
#include "TelepathyQt/_gen/channel-dispatch-operation.moc.hpp"
#include "TelepathyQt/_gen/channel-dispatch-operation-internal.moc.hpp"
#include "TelepathyQt/debug-internal.h"
#include "TelepathyQt/fake-handler-manager-internal.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace Tp
{
struct TP_QT_NO_EXPORT ChannelDispatchOperation::Private
{
Private(ChannelDispatchOperation *parent);
~Private();
static void introspectMain(Private *self);
void extractMainProps(const QVariantMap &props,
bool immutableProperties);
// Public object
ChannelDispatchOperation *parent;
// Context
AccountFactoryConstPtr accFactory;
ConnectionFactoryConstPtr connFactory;
ChannelFactoryConstPtr chanFactory;
ContactFactoryConstPtr contactFactory;
// Instance of generated interface class
Client::ChannelDispatchOperationInterface *baseInterface;
// Mandatory properties interface proxy
Client::DBus::PropertiesInterface *properties;
ReadinessHelper *readinessHelper;
// Introspection
QVariantMap immutableProperties;
ConnectionPtr connection;
AccountPtr account;
QList channels;
QStringList possibleHandlers;
bool gotPossibleHandlers;
};
ChannelDispatchOperation::Private::Private(ChannelDispatchOperation *parent)
: parent(parent),
baseInterface(new Client::ChannelDispatchOperationInterface(parent)),
properties(parent->interface()),
readinessHelper(parent->readinessHelper()),
gotPossibleHandlers(false)
{
debug() << "Creating new ChannelDispatchOperation:" << parent->objectPath();
parent->connect(baseInterface,
SIGNAL(Finished()),
SLOT(onFinished()));
parent->connect(baseInterface,
SIGNAL(ChannelLost(QDBusObjectPath,QString,QString)),
SLOT(onChannelLost(QDBusObjectPath,QString,QString)));
ReadinessHelper::Introspectables introspectables;
// As ChannelDispatchOperation does not have predefined statuses let's simulate one (0)
ReadinessHelper::Introspectable introspectableCore(
QSet() << 0, // makesSenseForStatuses
Features(), // dependsOnFeatures
QStringList(), // dependsOnInterfaces
(ReadinessHelper::IntrospectFunc) &Private::introspectMain,
this);
introspectables[FeatureCore] = introspectableCore;
readinessHelper->addIntrospectables(introspectables);
}
ChannelDispatchOperation::Private::~Private()
{
}
void ChannelDispatchOperation::Private::introspectMain(ChannelDispatchOperation::Private *self)
{
QVariantMap mainProps;
foreach (QString key, self->immutableProperties.keys()) {
if (key.startsWith(TP_QT_IFACE_CHANNEL_DISPATCH_OPERATION + QLatin1String("."))) {
QVariant value = self->immutableProperties.value(key);
mainProps.insert(
key.remove(TP_QT_IFACE_CHANNEL_DISPATCH_OPERATION + QLatin1String(".")),
value);
}
}
if (!self->channels.isEmpty() && mainProps.contains(QLatin1String("Account"))
&& mainProps.contains(QLatin1String("Connection"))
&& mainProps.contains(QLatin1String("Interfaces"))
&& mainProps.contains(QLatin1String("PossibleHandlers"))) {
debug() << "Supplied properties were sufficient, not introspecting"
<< self->parent->objectPath();
self->extractMainProps(mainProps, true);
return;
}
debug() << "Calling Properties::GetAll(ChannelDispatchOperation)";
QDBusPendingCallWatcher *watcher =
new QDBusPendingCallWatcher(
self->properties->GetAll(TP_QT_IFACE_CHANNEL_DISPATCH_OPERATION),
self->parent);
self->parent->connect(watcher,
SIGNAL(finished(QDBusPendingCallWatcher*)),
SLOT(gotMainProperties(QDBusPendingCallWatcher*)));
}
void ChannelDispatchOperation::Private::extractMainProps(const QVariantMap &props,
bool immutableProperties)
{
parent->setInterfaces(qdbus_cast(props.value(QLatin1String("Interfaces"))));
QList readyOps;
if (!connection && props.contains(QLatin1String("Connection"))) {
QDBusObjectPath connectionObjectPath =
qdbus_cast(props.value(QLatin1String("Connection")));
QString connectionBusName =
connectionObjectPath.path().mid(1).replace(QLatin1String("/"),
QLatin1String("."));
PendingReady *readyOp =
connFactory->proxy(connectionBusName, connectionObjectPath.path(),
chanFactory, contactFactory);
connection = ConnectionPtr::qObjectCast(readyOp->proxy());
readyOps.append(readyOp);
}
if (!account && props.contains(QLatin1String("Account"))) {
QDBusObjectPath accountObjectPath =
qdbus_cast(props.value(QLatin1String("Account")));
PendingReady *readyOp =
accFactory->proxy(TP_QT_ACCOUNT_MANAGER_BUS_NAME,
accountObjectPath.path(), connFactory, chanFactory, contactFactory);
account = AccountPtr::qObjectCast(readyOp->proxy());
readyOps.append(readyOp);
}
if (!immutableProperties) {
// If we're here, it means we had to introspect the object, and now for sure have the
// correct channels list, so let's overwrite the initial channels - but keep the refs around
// for a while as an optimization enabling the factory to still return the same ones instead
// of constructing everything anew. Note that this is not done at all in the case the
// immutable props and initial channels etc were sufficient.
QList saveChannels = channels;
channels.clear();
ChannelDetailsList channelDetailsList =
qdbus_cast(props.value(QLatin1String("Channels")));
ChannelPtr channel;
foreach (const ChannelDetails &channelDetails, channelDetailsList) {
PendingReady *readyOp =
chanFactory->proxy(connection,
channelDetails.channel.path(), channelDetails.properties);
channels.append(ChannelPtr::qObjectCast(readyOp->proxy()));
readyOps.append(readyOp);
}
// saveChannels goes out of scope now, so any initial channels which don't exist anymore are
// freed
}
if (props.contains(QLatin1String("PossibleHandlers"))) {
possibleHandlers = qdbus_cast(props.value(QLatin1String("PossibleHandlers")));
gotPossibleHandlers = true;
}
if (readyOps.isEmpty()) {
debug() << "No proxies to prepare for CDO" << parent->objectPath();
readinessHelper->setIntrospectCompleted(FeatureCore, true);
} else {
parent->connect(new PendingComposite(readyOps, ChannelDispatchOperationPtr(parent)),
SIGNAL(finished(Tp::PendingOperation*)),
SLOT(onProxiesPrepared(Tp::PendingOperation*)));
}
}
ChannelDispatchOperation::PendingClaim::PendingClaim(const ChannelDispatchOperationPtr &op,
const AbstractClientHandlerPtr &handler)
: PendingOperation(op),
mDispatchOp(op),
mHandler(handler)
{
debug() << "Invoking CDO.Claim";
connect(new PendingVoid(op->baseInterface()->Claim(), op),
SIGNAL(finished(Tp::PendingOperation*)),
SLOT(onClaimFinished(Tp::PendingOperation*)));
}
ChannelDispatchOperation::PendingClaim::~PendingClaim()
{
}
void ChannelDispatchOperation::PendingClaim::onClaimFinished(
PendingOperation *op)
{
if (!op->isError()) {
debug() << "CDO.Claim returned successfully, updating HandledChannels";
if (mHandler) {
// register the channels in HandledChannels
FakeHandlerManager::instance()->registerChannels(
mDispatchOp->channels());
}
setFinished();
} else {
warning() << "CDO.Claim failed with" << op->errorName() << "-" << op->errorMessage();
setFinishedWithError(op->errorName(), op->errorMessage());
}
}
/**
* \class ChannelDispatchOperation
* \ingroup clientchanneldispatchoperation
* \headerfile TelepathyQt/channel-dispatch-operation.h
*
* \brief The ChannelDispatchOperation class represents a Telepathy channel
* dispatch operation.
*
* One of the channel dispatcher's functions is to offer incoming channels to
* Approver clients for approval. An approver should generally ask the user
* whether they want to participate in the requested communication channels
* (join the chat or chatroom, answer the call, accept the file transfer, or
* whatever is appropriate). A collection of channels offered in this way
* is represented by a ChannelDispatchOperation object.
*
* If the user wishes to accept the communication channels, the approver
* should call handleWith() to indicate the user's or approver's preferred
* handler for the channels (the empty string indicates no particular
* preference, and will cause any suitable handler to be used).
*
* If the user wishes to reject the communication channels, or if the user
* accepts the channels and the approver will handle them itself, the approver
* should call claim(). If the resulting PendingOperation succeeds, the approver
* immediately has control over the channels as their primary handler,
* and may do anything with them (in particular, it may close them in whatever
* way seems most appropriate).
*
* There are various situations in which the channel dispatch operation will
* be closed, causing the DBusProxy::invalidated() signal to be emitted. If this
* happens, the approver should stop prompting the user.
*
* Because all approvers are launched simultaneously, the user might respond
* to another approver; if this happens, the invalidated signal will be
* emitted with the error code #TP_QT_ERROR_OBJECT_REMOVED.
*
* If a channel closes, the signal channelLost() is emitted. If all channels
* close, there is nothing more to dispatch, so the invalidated signal will be
* emitted with the error code #TP_QT_ERROR_OBJECT_REMOVED.
*
* If the channel dispatcher crashes or exits, the invalidated
* signal will be emitted with the error code
* #TP_QT_DBUS_ERROR_NAME_HAS_NO_OWNER. In a high-quality implementation,
* the dispatcher should be restarted, at which point it will create new
* channel dispatch operations for any undispatched channels, and the approver
* will be notified again.
*/
/**
* Feature representing the core that needs to become ready to make the
* ChannelDispatchOperation object usable.
*
* Note that this feature must be enabled in order to use most
* ChannelDispatchOperation methods.
*
* When calling isReady(), becomeReady(), this feature is implicitly added
* to the requested features.
*/
const Feature ChannelDispatchOperation::FeatureCore = Feature(QLatin1String(ChannelDispatchOperation::staticMetaObject.className()), 0, true);
/**
* Create a new channel dispatch operation object using the given \a bus, the given factories and
* the given initial channels.
*
* \param bus QDBusConnection to use.
* \param objectPath The channel dispatch operation object path.
* \param immutableProperties The channel dispatch operation immutable properties.
* \param initialChannels The channels this CDO has initially (further tracking is done internally).
* \param accountFactory The account factory to use.
* \param connectionFactory The connection factory to use.
* \param channelFactory The channel factory to use.
* \param contactFactory The contact factory to use.
* \return A ChannelDispatchOperationPtr object pointing to the newly created
* ChannelDispatchOperation object.
*/
ChannelDispatchOperationPtr ChannelDispatchOperation::create(const QDBusConnection &bus,
const QString &objectPath, const QVariantMap &immutableProperties,
const QList &initialChannels,
const AccountFactoryConstPtr &accountFactory,
const ConnectionFactoryConstPtr &connectionFactory,
const ChannelFactoryConstPtr &channelFactory,
const ContactFactoryConstPtr &contactFactory)
{
return ChannelDispatchOperationPtr(new ChannelDispatchOperation(
bus, objectPath, immutableProperties, initialChannels, accountFactory,
connectionFactory, channelFactory, contactFactory));
}
/**
* Construct a new channel dispatch operation object using the given \a bus, the given factories and
* the given initial channels.
*
* \param bus QDBusConnection to use
* \param objectPath The channel dispatch operation object path.
* \param immutableProperties The channel dispatch operation immutable properties.
* \param initialChannels The channels this CDO has initially (further tracking is done internally).
* \param accountFactory The account factory to use.
* \param connectionFactory The connection factory to use.
* \param channelFactory The channel factory to use.
* \param contactFactory The contact factory to use.
*/
ChannelDispatchOperation::ChannelDispatchOperation(const QDBusConnection &bus,
const QString &objectPath, const QVariantMap &immutableProperties,
const QList &initialChannels,
const AccountFactoryConstPtr &accountFactory,
const ConnectionFactoryConstPtr &connectionFactory,
const ChannelFactoryConstPtr &channelFactory,
const ContactFactoryConstPtr &contactFactory)
: StatefulDBusProxy(bus,
TP_QT_IFACE_CHANNEL_DISPATCHER,
objectPath, FeatureCore),
OptionalInterfaceFactory(this),
mPriv(new Private(this))
{
if (accountFactory->dbusConnection().name() != bus.name()) {
warning() << " The D-Bus connection in the account factory is not the proxy connection";
}
if (connectionFactory->dbusConnection().name() != bus.name()) {
warning() << " The D-Bus connection in the connection factory is not the proxy connection";
}
if (channelFactory->dbusConnection().name() != bus.name()) {
warning() << " The D-Bus connection in the channel factory is not the proxy connection";
}
mPriv->channels = initialChannels;
mPriv->accFactory = accountFactory;
mPriv->connFactory = connectionFactory;
mPriv->chanFactory = channelFactory;
mPriv->contactFactory = contactFactory;
mPriv->immutableProperties = immutableProperties;
}
/**
* Class destructor.
*/
ChannelDispatchOperation::~ChannelDispatchOperation()
{
delete mPriv;
}
/**
* Return the connection with which the channels for this dispatch
* operation are associated.
*
* This method requires ChannelDispatchOperation::FeatureCore to be ready.
*
* \return A pointer to the Connection object.
*/
ConnectionPtr ChannelDispatchOperation::connection() const
{
return mPriv->connection;
}
/**
* Return the account with which the connection and channels for this dispatch
* operation are associated.
*
* This method requires ChannelDispatchOperation::FeatureCore to be ready.
*
* \return A pointer to the Account object.
*/
AccountPtr ChannelDispatchOperation::account() const
{
return mPriv->account;
}
/**
* Return the channels to be dispatched.
*
* This method requires ChannelDispatchOperation::FeatureCore to be ready.
*
* \return A list of pointers to Channel objects.
*/
QList ChannelDispatchOperation::channels() const
{
if (!isReady()) {
warning() << "ChannelDispatchOperation::channels called with channel "
"not ready";
}
return mPriv->channels;
}
/**
* Return the well known bus names (starting with
* org.freedesktop.Telepathy.Client.) of the possible Handlers for this
* dispatch operation channels with the preferred handlers first.
*
* As a result, approvers should use the first handler by default, unless they
* have a reason to do otherwise.
*
* This method requires ChannelDispatchOperation::FeatureCore to be ready.
*
* \return List of possible handlers names.
*/
QStringList ChannelDispatchOperation::possibleHandlers() const
{
return mPriv->possibleHandlers;
}
/**
* Called by an approver to accept a channel bundle and request that the given
* handler be used to handle it.
*
* If successful, this method will cause the ChannelDispatchOperation object to
* disappear, emitting invalidated with error
* #TP_QT_ERROR_OBJECT_REMOVED.
*
* However, this method may fail because the dispatch has already been completed
* and the object has already gone. If this occurs, it indicates that another
* approver has asked for the bundle to be handled by a particular handler. The
* approver must not attempt to interact with the channels further in this case,
* unless it is separately invoked as the handler.
*
* Approvers which are also channel handlers should use claim() instead of
* this method to request that they can handle a channel bundle themselves.
*
* \param handler The well-known bus name (starting with
* org.freedesktop.Telepathy.Client.) of the channel handler that
* should handle the channel, or an empty string if
* the client has no preferred channel handler.
* \return A PendingOperation which will emit PendingOperation::finished
* when the call has finished.
*/
PendingOperation *ChannelDispatchOperation::handleWith(const QString &handler)
{
return new PendingVoid(
mPriv->baseInterface->HandleWith(handler),
ChannelDispatchOperationPtr(this));
}
/**
* Called by an approver to claim channels for closing them.
*
* \return A PendingOperation which will emit PendingOperation::finished
* when the call has finished.
*/
PendingOperation *ChannelDispatchOperation::claim()
{
return new PendingClaim(ChannelDispatchOperationPtr(this));
}
/**
* Called by an approver to claim channels for handling internally. If this
* method is called successfully, the \a handler becomes the
* handler for the channel, but does not have the
* AbstractClientHandler::handleChannels() method called on it.
*
* Approvers wishing to reject channels must call this method to claim ownership
* of them, and must not call requestClose() on the channels unless/until this
* method returns successfully.
*
* The channel dispatcher can't know how best to close arbitrary channel types,
* so it leaves it up to the approver to do so. For instance, for text channels
* it is necessary to acknowledge any messages that have already been displayed
* to the user first - ideally, the approver would display and then acknowledge
* the messages - or to call Channel::requestClose() if the destructive
* behaviour of that method is desired.
*
* Similarly, an approver for streamed media channels can close the channel with
* a reason (e.g. "busy") if desired. The channel dispatcher, which is designed
* to have no specific knowledge of particular channel types, can't do that.
*
* If successful, this method will cause the ChannelDispatchOperation object to
* disappear, emitting Finished, in the same way as for handleWith().
*
* This method may fail because the dispatch operation has already been
* completed. Again, see handleWith() for more details. The approver must not
* attempt to interact with the channels further in this case.
*
* \param handler The channel handler, that should remain registered during the
* lifetime of channels(), otherwise dispatching will fail if the
* channel dispatcher restarts.
* \return A PendingOperation which will emit PendingOperation::finished
* when the call has finished.
* \sa claim(), handleWith()
*/
PendingOperation *ChannelDispatchOperation::claim(const AbstractClientHandlerPtr &handler)
{
if (!handler->isRegistered()) {
return new PendingFailure(TP_QT_ERROR_INVALID_ARGUMENT,
QLatin1String("Handler must be registered for using claim(handler)"),
ChannelDispatchOperationPtr(this));
}
return new PendingClaim(ChannelDispatchOperationPtr(this),
handler);
}
/**
* \fn void ChannelDispatchOperation::channelLost(const ChannelPtr &channel,
* const QString &errorName, const QString &errorMessage);
*
* Emitted when a channel has closed before it could be claimed or handled. If this is
* emitted for the last remaining channel in a channel dispatch operation, it
* will immediately be followed by invalidated() with error
* #TP_QT_ERROR_OBJECT_REMOVED.
*
* \param channel The channel that was closed.
* \param error The name of a D-Bus error indicating why the channel closed.
* \param errorMessage The error message.
*/
/**
* Return the ChannelDispatchOperationInterface for this ChannelDispatchOperation
* class. This method is protected since the convenience methods provided by
* this class should always be used instead of the interface by users of the
* class.
*
* \return A pointer to the existing Client::ChannelDispatchOperationInterface object for this
* ChannelDispatchOperation object.
*/
Client::ChannelDispatchOperationInterface *ChannelDispatchOperation::baseInterface() const
{
return mPriv->baseInterface;
}
void ChannelDispatchOperation::onFinished()
{
debug() << "ChannelDispatchOperation finished and was removed";
invalidate(TP_QT_ERROR_OBJECT_REMOVED,
QLatin1String("ChannelDispatchOperation finished and was removed"));
}
void ChannelDispatchOperation::gotMainProperties(QDBusPendingCallWatcher *watcher)
{
QDBusPendingReply reply = *watcher;
// Watcher is NULL if we didn't have to introspect at all
if (!reply.isError()) {
debug() << "Got reply to Properties::GetAll(ChannelDispatchOperation)";
mPriv->extractMainProps(reply.value(), false);
} else {
mPriv->readinessHelper->setIntrospectCompleted(FeatureCore,
false, reply.error());
warning().nospace() << "Properties::GetAll(ChannelDispatchOperation) failed with "
<< reply.error().name() << ": " << reply.error().message();
}
}
void ChannelDispatchOperation::onChannelLost(
const QDBusObjectPath &channelObjectPath,
const QString &errorName, const QString &errorMessage)
{
foreach (const ChannelPtr &channel, mPriv->channels) {
if (channel->objectPath() == channelObjectPath.path()) {
emit channelLost(channel, errorName, errorMessage);
mPriv->channels.removeOne(channel);
return;
}
}
}
void ChannelDispatchOperation::onProxiesPrepared(Tp::PendingOperation *op)
{
if (op->isError()) {
warning() << "Preparing proxies for CDO" << objectPath() << "failed with"
<< op->errorName() << ":" << op->errorMessage();
mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false);
} else {
mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
}
}
} // Tp