/*
* 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/cli-channel-request-body.hpp"
#include "TelepathyQt4/_gen/cli-channel-request.moc.hpp"
#include "TelepathyQt4/_gen/channel-request.moc.hpp"
#include "TelepathyQt4/debug-internal.h"
#include
#include
#include
#include
#include
#include
namespace Tp
{
struct TELEPATHY_QT4_NO_EXPORT ChannelRequest::Private
{
Private(ChannelRequest *parent, const QVariantMap &immutableProperties,
const AccountFactoryConstPtr &, const ConnectionFactoryConstPtr &,
const ChannelFactoryConstPtr &, const ContactFactoryConstPtr &);
~Private();
static void introspectMain(Private *self);
// \param lastCall Is this the last call to extractMainProps ie. should actions that only must
// be done once be done in this call
void extractMainProps(const QVariantMap &props, bool lastCall);
// Public object
ChannelRequest *parent;
// Context
AccountFactoryConstPtr accFact;
ConnectionFactoryConstPtr connFact;
ChannelFactoryConstPtr chanFact;
ContactFactoryConstPtr contactFact;
// Instance of generated interface class
Client::ChannelRequestInterface *baseInterface;
// Mandatory properties interface proxy
Client::DBus::PropertiesInterface *properties;
QVariantMap immutableProperties;
ReadinessHelper *readinessHelper;
// Introspection
AccountPtr account;
QDateTime userActionTime;
QString preferredHandler;
QualifiedPropertyValueMapList requests;
bool propertiesDone;
};
ChannelRequest::Private::Private(ChannelRequest *parent,
const QVariantMap &immutableProperties,
const AccountFactoryConstPtr &accFact,
const ConnectionFactoryConstPtr &connFact,
const ChannelFactoryConstPtr &chanFact,
const ContactFactoryConstPtr &contactFact)
: parent(parent),
accFact(accFact),
connFact(connFact),
chanFact(chanFact),
contactFact(contactFact),
baseInterface(new Client::ChannelRequestInterface(parent)),
properties(parent->interface()),
immutableProperties(immutableProperties),
readinessHelper(parent->readinessHelper()),
propertiesDone(false)
{
debug() << "Creating new ChannelRequest:" << parent->objectPath();
parent->connect(baseInterface,
SIGNAL(Failed(QString,QString)),
SIGNAL(failed(QString,QString)));
parent->connect(baseInterface,
SIGNAL(Succeeded()),
SIGNAL(succeeded()));
ReadinessHelper::Introspectables introspectables;
// As ChannelRequest 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);
// For early access to the immutable properties through the friendly getters - will be called
// again with lastCall = true eventually, if/when becomeReady is called, though
QVariantMap mainProps;
foreach (QString key, immutableProperties.keys()) {
// The key.count thing is so that we don't match "org.fdo.Tp.CR.OptionalInterface.Prop" too
if (key.startsWith(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST "."))
&& key.count(QLatin1Char('.')) ==
QString::fromAscii(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".").count(QLatin1Char('.'))) {
QVariant value = immutableProperties.value(key);
mainProps.insert(key.remove(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".")), value);
}
}
extractMainProps(mainProps, false);
}
ChannelRequest::Private::~Private()
{
}
void ChannelRequest::Private::introspectMain(ChannelRequest::Private *self)
{
QVariantMap props;
QString key;
bool needIntrospectMainProps = false;
const char *propertiesNames[] = { "Account", "UserActionTime",
"PreferredHandler", "Requests", "Interfaces",
NULL };
for (unsigned i = 0; propertiesNames[i] != NULL; ++i) {
key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".");
key += QLatin1String(propertiesNames[i]);
if (!self->immutableProperties.contains(key)) {
needIntrospectMainProps = true;
break;
}
props.insert(QLatin1String(propertiesNames[i]),
self->immutableProperties[key]);
}
if (needIntrospectMainProps) {
debug() << "Calling Properties::GetAll(ChannelRequest)";
QDBusPendingCallWatcher *watcher =
new QDBusPendingCallWatcher(
self->properties->GetAll(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST)),
self->parent);
// FIXME: This is a Qt bug fixed upstream, should be in the next Qt release.
// We should not need to check watcher->isFinished() here, remove the
// check when a fixed Qt version is released.
if (watcher->isFinished()) {
self->parent->gotMainProperties(watcher);
} else {
self->parent->connect(watcher,
SIGNAL(finished(QDBusPendingCallWatcher*)),
SLOT(gotMainProperties(QDBusPendingCallWatcher*)));
}
} else {
self->extractMainProps(props, true);
}
}
void ChannelRequest::Private::extractMainProps(const QVariantMap &props, bool lastCall)
{
PendingReady *readyOp = 0;
if (props.contains(QLatin1String("Account"))) {
QDBusObjectPath accountObjectPath =
qdbus_cast(props.value(QLatin1String("Account")));
if (!account.isNull()) {
if (accountObjectPath.path() == account->objectPath()) {
// Most often a no-op, but we want this to guarantee the old behavior in all cases
readyOp = account->becomeReady();
} else {
warning() << "The account" << accountObjectPath.path() << "was not the expected"
<< account->objectPath() << "for CR" << parent->objectPath();
// Construct a new one instead
account.reset();
}
}
// We need to check again because we might have dropped the expected account just a sec ago
if (account.isNull() && !accountObjectPath.path().isEmpty()) {
if (!accFact.isNull()) {
readyOp = accFact->proxy(
QLatin1String(TELEPATHY_ACCOUNT_MANAGER_BUS_NAME), accountObjectPath.path(),
connFact, chanFact, contactFact);
account = AccountPtr::qObjectCast(readyOp->proxy());
} else {
account = Account::create(
QLatin1String(TELEPATHY_ACCOUNT_MANAGER_BUS_NAME),
accountObjectPath.path(), connFact, chanFact, contactFact);
readyOp = account->becomeReady();
}
}
}
// FIXME See http://bugs.freedesktop.org/show_bug.cgi?id=21690
uint stamp = (uint) qdbus_cast(props.value(QLatin1String("UserActionTime")));
if (stamp != 0) {
userActionTime = QDateTime::fromTime_t(stamp);
}
preferredHandler = qdbus_cast(props.value(QLatin1String("PreferredHandler")));
requests = qdbus_cast(props.value(QLatin1String("Requests")));
parent->setInterfaces(qdbus_cast(props[QLatin1String("Interfaces")]));
readinessHelper->setInterfaces(parent->interfaces());
if (lastCall) {
propertiesDone = true;
}
if (account) {
parent->connect(readyOp,
SIGNAL(finished(Tp::PendingOperation*)),
SLOT(onAccountReady(Tp::PendingOperation*)));
} else if (lastCall) {
warning() << "No account for ChannelRequest" << parent->objectPath();
readinessHelper->setIntrospectCompleted(FeatureCore, true);
}
}
/**
* \class ChannelRequest
* \ingroup clientchannelrequest
* \headerfile TelepathyQt4/channel-request.h
*
* \brief The ChannelRequest class provides an object representing a Telepathy
* channel request.
*
* A channel request is an object in the channel dispatcher representing an
* ongoing request for some channels to be created or found. There can be any
* number of channel request objects at the same time.
*
* A channel request can be cancelled by any client (not just the one that
* requested it). This means that the channel dispatcher will close the
* resulting channel, or refrain from requesting it at all, rather than
* dispatching it to a handler.
*/
/**
* Feature representing the core that needs to become ready to make the
* ChannelRequest object usable.
*
* Note that this feature must be enabled in order to use most
* ChannelRequest methods.
*
* When calling isReady(), becomeReady(), this feature is implicitly added
* to the requested features.
*/
const Feature ChannelRequest::FeatureCore = Feature(QLatin1String(ChannelRequest::staticMetaObject.className()), 0, true);
/**
* Create a new channel request object using the given \a bus and the given factories.
*
* \param objectPath The channel request object path.
* \param immutableProperties The immutable properties of the channel request.
* \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 ChannelRequestPtr pointing to the newly created ChannelRequest.
*/
ChannelRequestPtr ChannelRequest::create(const QDBusConnection &bus, const QString &objectPath,
const QVariantMap &immutableProperties,
const AccountFactoryConstPtr &accountFactory,
const ConnectionFactoryConstPtr &connectionFactory,
const ChannelFactoryConstPtr &channelFactory,
const ContactFactoryConstPtr &contactFactory)
{
return ChannelRequestPtr(new ChannelRequest(bus, objectPath, immutableProperties,
accountFactory, connectionFactory, channelFactory, contactFactory));
}
/**
* Create a new channel request object for the given \a account.
*
* The returned instance will use factories from the account.
*
* \param account The account that the request was made through.
* \param objectPath The channel request object path.
* \param immutableProperties The immutable properties of the channel request.
* \return A ChannelRequestPtr pointing to the newly created ChannelRequest.
*/
ChannelRequestPtr ChannelRequest::create(const AccountPtr &account, const QString &objectPath,
const QVariantMap &immutableProperties)
{
return ChannelRequestPtr(new ChannelRequest(account, objectPath, immutableProperties));
}
/**
* Construct a new channel request object using the given \a bus and the given factories.
*
* \param bus QDBusConnection to use.
* \param objectPath The channel request object path.
* \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.
* \param immutableProperties The immutable properties of the channel request.
* \return A ChannelRequestPtr pointing to the newly created ChannelRequest.
*/
ChannelRequest::ChannelRequest(const QDBusConnection &bus,
const QString &objectPath, const QVariantMap &immutableProperties,
const AccountFactoryConstPtr &accountFactory,
const ConnectionFactoryConstPtr &connectionFactory,
const ChannelFactoryConstPtr &channelFactory,
const ContactFactoryConstPtr &contactFactory)
: StatefulDBusProxy(bus,
QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER),
objectPath, FeatureCore),
OptionalInterfaceFactory(this),
mPriv(new Private(this, immutableProperties, accountFactory, connectionFactory,
channelFactory, contactFactory))
{
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";
}
}
/**
* Construct a new channel request object using the given \a account.
*
* The constructed instance will use the factories from the account.
*
* \param account Account to use.
* \param objectPath The channel request object path.
* \param immutableProperties The immutable properties of the channel request.
* \return A ChannelRequestPtr pointing to the newly created ChannelRequest.
*/
ChannelRequest::ChannelRequest(const AccountPtr &account,
const QString &objectPath, const QVariantMap &immutableProperties)
: StatefulDBusProxy(account->dbusConnection(),
QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER),
objectPath, FeatureCore),
OptionalInterfaceFactory(this),
mPriv(new Private(this, immutableProperties, AccountFactoryPtr(),
account->connectionFactory(),
account->channelFactory(),
account->contactFactory()))
{
mPriv->account = account;
}
/**
* Class destructor.
*/
ChannelRequest::~ChannelRequest()
{
delete mPriv;
}
/**
* Return the Account on which this request was made.
*
* This method can be used even before the ChannelRequest is ready, in which case the account object
* corresponding to the immutable properties is returned. In this case, the Account object is not
* necessarily ready either. This is useful for eg. matching ChannelRequests from
* ClientHandlerInterface::addRequest() with existing accounts in the application: either by object
* path, or if account factories are in use, even by object identity.
*
* If the account is not provided in the immutable properties, this will only return a non-\c NULL
* AccountPtr once ChannelRequest::FeatureCore is ready on this object.
*
* \return The account on which this request was made.
*/
AccountPtr ChannelRequest::account() const
{
return mPriv->account;
}
/**
* Return the time at which the user action occurred, or 0 if this channel
* request is for some reason not involving user action.
*
* Unix developers: this corresponds to the _NET_WM_USER_TIME property in EWMH.
*
* This property is set when the channel request is created, and can never
* change.
*
* This method can be used even before the ChannelRequest is ready: in this case, the user action
* time from the immutable properties, if any, is returned.
*
* \return The time at which the user action occurred.
*/
QDateTime ChannelRequest::userActionTime() const
{
return mPriv->userActionTime;
}
/**
* Return either the well-known bus name (starting with
* org.freedesktop.Telepathy.Client.) of the preferred handler for this channel,
* or an empty string to indicate that any handler would be acceptable.
*
* This property is set when the channel request is created, and can never
* change.
*
* This method can be used even before the ChannelRequest is ready: in this case, the preferred
* handler from the immutable properties, if any, is returned.
*
* \return The preferred handler or an empty string if any handler would be
* acceptable.
*/
QString ChannelRequest::preferredHandler() const
{
return mPriv->preferredHandler;
}
/**
* Return the desirable properties for the channel or channels to be created, as specified when
* placing the request in the first place.
*
* This property is set when the channel request is created, and can never
* change.
*
* This method can be used even before the ChannelRequest is ready: in this case, the requested
* channel properties from the immutable properties, if any, are returned. This is useful for e.g.
* matching ChannelRequests from ClientHandlerInterface::addRequest() with existing requests in the
* application (by the target ID or handle, most likely).
*
* \return The requested desirable channel properties.
*/
QualifiedPropertyValueMapList ChannelRequest::requests() const
{
return mPriv->requests;
}
/**
* Return all of the immutable properties passed to this object when created.
*
* This is useful for e.g. getting to domain-specific properties of channel requests.
*
* \return The immutable properties.
*/
QVariantMap ChannelRequest::immutableProperties() const
{
QVariantMap props = mPriv->immutableProperties;
if (!account().isNull()) {
props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".Account"),
QVariant::fromValue(QDBusObjectPath(account()->objectPath())));
}
if (userActionTime().isValid()) {
props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".UserActionTime"),
QVariant::fromValue(userActionTime().toTime_t()));
}
if (!preferredHandler().isNull()) {
props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".PreferredHandler"),
preferredHandler());
}
if (!requests().isEmpty()) {
props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".Requests"),
QVariant::fromValue(requests()));
}
props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".Interfaces"),
QVariant::fromValue(interfaces()));
return props;
}
/**
* Cancel the channel request.
*
* If failed() is emitted in response to this method, the error will be
* #TELEPATHY_ERROR_CANCELLED.
*
* If the channel has already been dispatched to a handler, then it's too late
* to call this method, and the channel request will no longer exist.
*
* \return A PendingOperation which will emit PendingOperation::finished
* when the call has finished.
*/
PendingOperation *ChannelRequest::cancel()
{
return new PendingVoid(mPriv->baseInterface->Cancel(),
ChannelRequestPtr(this));
}
/**
* Proceed with the channel request.
*
* The client that created this object calls this method when it has connected
* signal handlers for succeeded() and failed(). Note that this is done
* automatically when using PendingChannelRequest.
*
* \return A PendingOperation which will emit PendingOperation::finished
* when the call has finished.
*/
PendingOperation *ChannelRequest::proceed()
{
return new PendingVoid(mPriv->baseInterface->Proceed(),
ChannelRequestPtr(this));
}
/**
* Return the ChannelRequestInterface for this ChannelRequest 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 ChannelRequestInterface for this
* ChannelRequest
*/
Client::ChannelRequestInterface *ChannelRequest::baseInterface() const
{
return mPriv->baseInterface;
}
/**
* \fn void ChannelRequest::failed(const QString &errorName,
* const QString &errorMessage);
*
* This signal is emitted when the channel request has failed. No further
* methods must not be called on it.
*
* \param errorName The name of a D-Bus error.
* \param errorMessage The error message.
*/
/**
* \fn void ChannelRequest::succeeded();
*
* This signals is emitted when the channel request has succeeded. No further
* methods must not be called on it.
*/
void ChannelRequest::gotMainProperties(QDBusPendingCallWatcher *watcher)
{
QDBusPendingReply reply = *watcher;
QVariantMap props;
if (!reply.isError()) {
debug() << "Got reply to Properties::GetAll(ChannelRequest)";
props = reply.value();
mPriv->extractMainProps(props, true);
} else {
mPriv->readinessHelper->setIntrospectCompleted(FeatureCore,
false, reply.error());
warning().nospace() << "Properties::GetAll(ChannelRequest) failed with "
<< reply.error().name() << ": " << reply.error().message();
}
watcher->deleteLater();
}
void ChannelRequest::onAccountReady(PendingOperation *op)
{
if (op->isError()) {
warning() << "Unable to make ChannelRequest.Account ready";
mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false,
op->errorName(), op->errorMessage());
return;
}
if (mPriv->propertiesDone && !isReady()) {
mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
}
}
} // Tp