diff options
Diffstat (limited to 'qt4/TelepathyQt4/client-registrar.cpp')
-rw-r--r-- | qt4/TelepathyQt4/client-registrar.cpp | 1038 |
1 files changed, 1038 insertions, 0 deletions
diff --git a/qt4/TelepathyQt4/client-registrar.cpp b/qt4/TelepathyQt4/client-registrar.cpp new file mode 100644 index 000000000..0276c7df8 --- /dev/null +++ b/qt4/TelepathyQt4/client-registrar.cpp @@ -0,0 +1,1038 @@ +/** + * This file is part of TelepathyQt4 + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-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 <TelepathyQt4/ClientRegistrar> +#include "TelepathyQt4/client-registrar-internal.h" + +#include "TelepathyQt4/_gen/client-registrar.moc.hpp" +#include "TelepathyQt4/_gen/client-registrar-internal.moc.hpp" + +#include "TelepathyQt4/channel-factory.h" +#include "TelepathyQt4/debug-internal.h" +#include "TelepathyQt4/request-temporary-handler-internal.h" + +#include <TelepathyQt4/Account> +#include <TelepathyQt4/AccountManager> +#include <TelepathyQt4/Channel> +#include <TelepathyQt4/ChannelDispatchOperation> +#include <TelepathyQt4/ChannelRequest> +#include <TelepathyQt4/Connection> +#include <TelepathyQt4/MethodInvocationContext> +#include <TelepathyQt4/PendingComposite> +#include <TelepathyQt4/PendingReady> + +namespace Tp +{ + +class HandleChannelsInvocationContext : public MethodInvocationContext<> +{ + Q_DISABLE_COPY(HandleChannelsInvocationContext) + +public: + typedef void (*FinishedCb)(const MethodInvocationContextPtr<> &context, + const QList<ChannelPtr> &channels, + void *data); + + static MethodInvocationContextPtr<> create(const QDBusConnection &bus, + const QDBusMessage &message, const QList<ChannelPtr> &channels, + FinishedCb finishedCb, void *finishedCbData) + { + return SharedPtr<MethodInvocationContext<> >( + new HandleChannelsInvocationContext(bus, message, channels, + finishedCb, finishedCbData)); + } + +private: + HandleChannelsInvocationContext(const QDBusConnection &connection, + const QDBusMessage &message, const QList<ChannelPtr> &channels, + FinishedCb finishedCb, void *finishedCbData) + : MethodInvocationContext<>(connection, message), + mChannels(channels), + mFinishedCb(finishedCb), + mFinishedCbData(finishedCbData) + { + } + + void onFinished() + { + if (mFinishedCb) { + mFinishedCb(MethodInvocationContextPtr<>(this), mChannels, mFinishedCbData); + } + } + + QList<ChannelPtr> mChannels; + FinishedCb mFinishedCb; + void *mFinishedCbData; +}; + +ClientAdaptor::ClientAdaptor(ClientRegistrar *registrar, const QStringList &interfaces, + QObject *parent) + : QDBusAbstractAdaptor(parent), + mRegistrar(registrar), + mInterfaces(interfaces) +{ +} + +ClientAdaptor::~ClientAdaptor() +{ +} + +ClientObserverAdaptor::ClientObserverAdaptor(ClientRegistrar *registrar, + AbstractClientObserver *client, + QObject *parent) + : QDBusAbstractAdaptor(parent), + mRegistrar(registrar), + mBus(registrar->dbusConnection()), + mClient(client) +{ +} + +ClientObserverAdaptor::~ClientObserverAdaptor() +{ +} + +void ClientObserverAdaptor::ObserveChannels(const QDBusObjectPath &accountPath, + const QDBusObjectPath &connectionPath, + const Tp::ChannelDetailsList &channelDetailsList, + const QDBusObjectPath &dispatchOperationPath, + const Tp::ObjectPathList &requestsSatisfied, + const QVariantMap &observerInfo, + const QDBusMessage &message) +{ + debug() << "ObserveChannels: account:" << accountPath.path() << + ", connection:" << connectionPath.path(); + + AccountFactoryConstPtr accFactory = mRegistrar->accountFactory(); + ConnectionFactoryConstPtr connFactory = mRegistrar->connectionFactory(); + ChannelFactoryConstPtr chanFactory = mRegistrar->channelFactory(); + ContactFactoryConstPtr contactFactory = mRegistrar->contactFactory(); + + SharedPtr<InvocationData> invocation(new InvocationData()); + + QList<PendingOperation *> readyOps; + + PendingReady *accReady = accFactory->proxy(QLatin1String(TELEPATHY_ACCOUNT_MANAGER_BUS_NAME), + accountPath.path(), + connFactory, + chanFactory, + contactFactory); + invocation->acc = AccountPtr::qObjectCast(accReady->proxy()); + readyOps.append(accReady); + + QString connectionBusName = connectionPath.path().mid(1).replace( + QLatin1String("/"), QLatin1String(".")); + PendingReady *connReady = connFactory->proxy(connectionBusName, connectionPath.path(), + chanFactory, contactFactory); + invocation->conn = ConnectionPtr::qObjectCast(connReady->proxy()); + readyOps.append(connReady); + + foreach (const ChannelDetails &channelDetails, channelDetailsList) { + PendingReady *chanReady = chanFactory->proxy(invocation->conn, + channelDetails.channel.path(), channelDetails.properties); + ChannelPtr channel = ChannelPtr::qObjectCast(chanReady->proxy()); + invocation->chans.append(channel); + readyOps.append(chanReady); + } + + // Yes, we don't give the choice of making CDO and CR ready or not - however, readifying them is + // 0-1 D-Bus calls each, for CR mostly 0 - and their constructors start making them ready + // automatically, so we wouldn't save any D-Bus traffic anyway + + if (!dispatchOperationPath.path().isEmpty() && dispatchOperationPath.path() != QLatin1String("/")) { + QVariantMap props; + + // TODO: push to tp spec having all of the CDO immutable props be contained in observerInfo + // so we don't have to introspect the CDO either - then we can pretty much do: + // + // props = qdbus_cast<QVariantMap>( + // observerInfo.value(QLatin1String("dispatch-operation-properties"))); + // + // Currently something like the following can be used for testing the CDO "we've got + // everything we need" codepath: + // + // props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Account"), + // QVariant::fromValue(QDBusObjectPath(accountPath.path()))); + // props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Connection"), + // QVariant::fromValue(QDBusObjectPath(connectionPath.path()))); + // props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Interfaces"), + // QVariant::fromValue(QStringList())); + // props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".PossibleHandlers"), + // QVariant::fromValue(QStringList())); + + invocation->dispatchOp = ChannelDispatchOperation::create(mBus, dispatchOperationPath.path(), + props, + invocation->chans, + accFactory, + connFactory, + chanFactory, + contactFactory); + readyOps.append(invocation->dispatchOp->becomeReady()); + } + + invocation->observerInfo = AbstractClientObserver::ObserverInfo(observerInfo); + + ObjectImmutablePropertiesMap reqPropsMap = qdbus_cast<ObjectImmutablePropertiesMap>( + observerInfo.value(QLatin1String("request-properties"))); + foreach (const QDBusObjectPath &reqPath, requestsSatisfied) { + ChannelRequestPtr channelRequest = ChannelRequest::create(invocation->acc, + reqPath.path(), reqPropsMap.value(reqPath)); + invocation->chanReqs.append(channelRequest); + readyOps.append(channelRequest->becomeReady()); + } + + invocation->ctx = MethodInvocationContextPtr<>(new MethodInvocationContext<>(mBus, message)); + + invocation->readyOp = new PendingComposite(readyOps, invocation->ctx); + connect(invocation->readyOp, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onReadyOpFinished(Tp::PendingOperation*))); + + mInvocations.append(invocation); + + debug() << "Preparing proxies for ObserveChannels of" << channelDetailsList.size() << "channels" + << "for client" << mClient; +} + +void ClientObserverAdaptor::onReadyOpFinished(Tp::PendingOperation *op) +{ + Q_ASSERT(!mInvocations.isEmpty()); + Q_ASSERT(op->isFinished()); + + for (QLinkedList<SharedPtr<InvocationData> >::iterator i = mInvocations.begin(); + i != mInvocations.end(); ++i) { + if ((*i)->readyOp != op) { + continue; + } + + (*i)->readyOp = 0; + + if (op->isError()) { + warning() << "Preparing proxies for ObserveChannels failed with" << op->errorName() + << op->errorMessage(); + (*i)->error = op->errorName(); + (*i)->message = op->errorMessage(); + } + + break; + } + + while (!mInvocations.isEmpty() && !mInvocations.first()->readyOp) { + SharedPtr<InvocationData> invocation = mInvocations.takeFirst(); + + if (!invocation->error.isEmpty()) { + // We guarantee that the proxies were ready - so we can't invoke the client if they + // weren't made ready successfully. Fix the introspection code if this happens :) + invocation->ctx->setFinishedWithError(invocation->error, invocation->message); + continue; + } + + debug() << "Invoking application observeChannels with" << invocation->chans.size() + << "channels on" << mClient; + + mClient->observeChannels(invocation->ctx, invocation->acc, invocation->conn, + invocation->chans, invocation->dispatchOp, invocation->chanReqs, + invocation->observerInfo); + } +} + +ClientApproverAdaptor::ClientApproverAdaptor(ClientRegistrar *registrar, + AbstractClientApprover *client, + QObject *parent) + : QDBusAbstractAdaptor(parent), + mRegistrar(registrar), + mBus(registrar->dbusConnection()), + mClient(client) +{ +} + +ClientApproverAdaptor::~ClientApproverAdaptor() +{ +} + +void ClientApproverAdaptor::AddDispatchOperation(const Tp::ChannelDetailsList &channelDetailsList, + const QDBusObjectPath &dispatchOperationPath, + const QVariantMap &properties, + const QDBusMessage &message) +{ + AccountFactoryConstPtr accFactory = mRegistrar->accountFactory(); + ConnectionFactoryConstPtr connFactory = mRegistrar->connectionFactory(); + ChannelFactoryConstPtr chanFactory = mRegistrar->channelFactory(); + ContactFactoryConstPtr contactFactory = mRegistrar->contactFactory(); + + QList<PendingOperation *> readyOps; + + QDBusObjectPath connectionPath = qdbus_cast<QDBusObjectPath>( + properties.value( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Connection"))); + debug() << "addDispatchOperation: connection:" << connectionPath.path(); + QString connectionBusName = connectionPath.path().mid(1).replace( + QLatin1String("/"), QLatin1String(".")); + PendingReady *connReady = connFactory->proxy(connectionBusName, connectionPath.path(), chanFactory, + contactFactory); + ConnectionPtr connection = ConnectionPtr::qObjectCast(connReady->proxy()); + readyOps.append(connReady); + + SharedPtr<InvocationData> invocation(new InvocationData); + + foreach (const ChannelDetails &channelDetails, channelDetailsList) { + PendingReady *chanReady = chanFactory->proxy(connection, channelDetails.channel.path(), + channelDetails.properties); + invocation->chans.append(ChannelPtr::qObjectCast(chanReady->proxy())); + readyOps.append(chanReady); + } + + invocation->dispatchOp = ChannelDispatchOperation::create(mBus, + dispatchOperationPath.path(), properties, invocation->chans, accFactory, connFactory, + chanFactory, contactFactory); + readyOps.append(invocation->dispatchOp->becomeReady()); + + invocation->ctx = MethodInvocationContextPtr<>(new MethodInvocationContext<>(mBus, message)); + + invocation->readyOp = new PendingComposite(readyOps, invocation->ctx); + connect(invocation->readyOp, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onReadyOpFinished(Tp::PendingOperation*))); + + mInvocations.append(invocation); +} + +void ClientApproverAdaptor::onReadyOpFinished(Tp::PendingOperation *op) +{ + Q_ASSERT(!mInvocations.isEmpty()); + Q_ASSERT(op->isFinished()); + + for (QLinkedList<SharedPtr<InvocationData> >::iterator i = mInvocations.begin(); + i != mInvocations.end(); ++i) { + if ((*i)->readyOp != op) { + continue; + } + + (*i)->readyOp = 0; + + if (op->isError()) { + warning() << "Preparing proxies for AddDispatchOperation failed with" << op->errorName() + << op->errorMessage(); + (*i)->error = op->errorName(); + (*i)->message = op->errorMessage(); + } + + break; + } + + while (!mInvocations.isEmpty() && !mInvocations.first()->readyOp) { + SharedPtr<InvocationData> invocation = mInvocations.takeFirst(); + + if (!invocation->error.isEmpty()) { + // We guarantee that the proxies were ready - so we can't invoke the client if they + // weren't made ready successfully. Fix the introspection code if this happens :) + invocation->ctx->setFinishedWithError(invocation->error, invocation->message); + continue; + } + + debug() << "Invoking application addDispatchOperation with CDO" + << invocation->dispatchOp->objectPath() << "on" << mClient; + + mClient->addDispatchOperation(invocation->ctx, invocation->dispatchOp); + } +} + +QHash<QPair<QString, QString>, QList<ClientHandlerAdaptor *> > ClientHandlerAdaptor::mAdaptorsForConnection; + +ClientHandlerAdaptor::ClientHandlerAdaptor(ClientRegistrar *registrar, + AbstractClientHandler *client, + QObject *parent) + : QDBusAbstractAdaptor(parent), + mRegistrar(registrar), + mBus(registrar->dbusConnection()), + mClient(client) +{ + QList<ClientHandlerAdaptor *> &handlerAdaptors = + mAdaptorsForConnection[qMakePair(mBus.name(), mBus.baseService())]; + handlerAdaptors.append(this); +} + +ClientHandlerAdaptor::~ClientHandlerAdaptor() +{ + QPair<QString, QString> busId = qMakePair(mBus.name(), mBus.baseService()); + QList<ClientHandlerAdaptor *> &handlerAdaptors = + mAdaptorsForConnection[busId]; + handlerAdaptors.removeOne(this); + if (handlerAdaptors.isEmpty()) { + mAdaptorsForConnection.remove(busId); + } +} + +void ClientHandlerAdaptor::HandleChannels(const QDBusObjectPath &accountPath, + const QDBusObjectPath &connectionPath, + const Tp::ChannelDetailsList &channelDetailsList, + const Tp::ObjectPathList &requestsSatisfied, + qulonglong userActionTime_t, + const QVariantMap &handlerInfo, + const QDBusMessage &message) +{ + debug() << "HandleChannels: account:" << accountPath.path() << + ", connection:" << connectionPath.path(); + + AccountFactoryConstPtr accFactory = mRegistrar->accountFactory(); + ConnectionFactoryConstPtr connFactory = mRegistrar->connectionFactory(); + ChannelFactoryConstPtr chanFactory = mRegistrar->channelFactory(); + ContactFactoryConstPtr contactFactory = mRegistrar->contactFactory(); + + SharedPtr<InvocationData> invocation(new InvocationData()); + QList<PendingOperation *> readyOps; + + RequestTemporaryHandler *tempHandler = dynamic_cast<RequestTemporaryHandler *>(mClient); + if (tempHandler) { + debug() << " This is a temporary handler for the Request & Handle API," + << "giving an early signal of the invocation"; + tempHandler->setDBusHandlerInvoked(); + } + + PendingReady *accReady = accFactory->proxy(QLatin1String(TELEPATHY_ACCOUNT_MANAGER_BUS_NAME), + accountPath.path(), + connFactory, + chanFactory, + contactFactory); + invocation->acc = AccountPtr::qObjectCast(accReady->proxy()); + readyOps.append(accReady); + + QString connectionBusName = connectionPath.path().mid(1).replace( + QLatin1String("/"), QLatin1String(".")); + PendingReady *connReady = connFactory->proxy(connectionBusName, connectionPath.path(), + chanFactory, contactFactory); + invocation->conn = ConnectionPtr::qObjectCast(connReady->proxy()); + readyOps.append(connReady); + + foreach (const ChannelDetails &channelDetails, channelDetailsList) { + PendingReady *chanReady = chanFactory->proxy(invocation->conn, + channelDetails.channel.path(), channelDetails.properties); + ChannelPtr channel = ChannelPtr::qObjectCast(chanReady->proxy()); + invocation->chans.append(channel); + readyOps.append(chanReady); + } + + invocation->handlerInfo = AbstractClientHandler::HandlerInfo(handlerInfo); + + ObjectImmutablePropertiesMap reqPropsMap = qdbus_cast<ObjectImmutablePropertiesMap>( + handlerInfo.value(QLatin1String("request-properties"))); + foreach (const QDBusObjectPath &reqPath, requestsSatisfied) { + ChannelRequestPtr channelRequest = ChannelRequest::create(invocation->acc, + reqPath.path(), reqPropsMap.value(reqPath)); + invocation->chanReqs.append(channelRequest); + readyOps.append(channelRequest->becomeReady()); + } + + // FIXME See http://bugs.freedesktop.org/show_bug.cgi?id=21690 + if (userActionTime_t != 0) { + invocation->time = QDateTime::fromTime_t((uint) userActionTime_t); + } + + invocation->ctx = HandleChannelsInvocationContext::create(mBus, message, + invocation->chans, + reinterpret_cast<HandleChannelsInvocationContext::FinishedCb>( + &ClientHandlerAdaptor::onContextFinished), + this); + + invocation->readyOp = new PendingComposite(readyOps, invocation->ctx); + connect(invocation->readyOp, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onReadyOpFinished(Tp::PendingOperation*))); + + mInvocations.append(invocation); + + debug() << "Preparing proxies for HandleChannels of" << channelDetailsList.size() << "channels" + << "for client" << mClient; +} + +void ClientHandlerAdaptor::onReadyOpFinished(Tp::PendingOperation *op) +{ + Q_ASSERT(!mInvocations.isEmpty()); + Q_ASSERT(op->isFinished()); + + for (QLinkedList<SharedPtr<InvocationData> >::iterator i = mInvocations.begin(); + i != mInvocations.end(); ++i) { + if ((*i)->readyOp != op) { + continue; + } + + (*i)->readyOp = 0; + + if (op->isError()) { + warning() << "Preparing proxies for HandleChannels failed with" << op->errorName() + << op->errorMessage(); + (*i)->error = op->errorName(); + (*i)->message = op->errorMessage(); + } + + break; + } + + while (!mInvocations.isEmpty() && !mInvocations.first()->readyOp) { + SharedPtr<InvocationData> invocation = mInvocations.takeFirst(); + + if (!invocation->error.isEmpty()) { + RequestTemporaryHandler *tempHandler = dynamic_cast<RequestTemporaryHandler *>(mClient); + if (tempHandler) { + debug() << " This is a temporary handler for the Request & Handle API, indicating failure"; + tempHandler->setDBusHandlerErrored(invocation->error, invocation->message); + } + + // We guarantee that the proxies were ready - so we can't invoke the client if they + // weren't made ready successfully. Fix the introspection code if this happens :) + invocation->ctx->setFinishedWithError(invocation->error, invocation->message); + continue; + } + + debug() << "Invoking application handleChannels with" << invocation->chans.size() + << "channels on" << mClient; + + mClient->handleChannels(invocation->ctx, invocation->acc, invocation->conn, + invocation->chans, invocation->chanReqs, invocation->time, invocation->handlerInfo); + } +} + +void ClientHandlerAdaptor::onContextFinished( + const MethodInvocationContextPtr<> &context, + const QList<ChannelPtr> &channels, ClientHandlerAdaptor *self) +{ + if (!context->isError()) { + debug() << "HandleChannels context finished successfully, " + "updating handled channels"; + + // register the channels in FakeHandlerManager so we report HandledChannels correctly + FakeHandlerManager::instance()->registerChannels(channels); + } +} + +ClientHandlerRequestsAdaptor::ClientHandlerRequestsAdaptor( + ClientRegistrar *registrar, + AbstractClientHandler *client, + QObject *parent) + : QDBusAbstractAdaptor(parent), + mRegistrar(registrar), + mBus(registrar->dbusConnection()), + mClient(client) +{ +} + +ClientHandlerRequestsAdaptor::~ClientHandlerRequestsAdaptor() +{ +} + +void ClientHandlerRequestsAdaptor::AddRequest( + const QDBusObjectPath &request, + const QVariantMap &requestProperties, + const QDBusMessage &message) +{ + debug() << "AddRequest:" << request.path(); + message.setDelayedReply(true); + mBus.send(message.createReply()); + mClient->addRequest(ChannelRequest::create(mBus, + request.path(), requestProperties, + mRegistrar->accountFactory(), + mRegistrar->connectionFactory(), + mRegistrar->channelFactory(), + mRegistrar->contactFactory())); +} + +void ClientHandlerRequestsAdaptor::RemoveRequest( + const QDBusObjectPath &request, + const QString &errorName, const QString &errorMessage, + const QDBusMessage &message) +{ + debug() << "RemoveRequest:" << request.path() << "-" << errorName + << "-" << errorMessage; + message.setDelayedReply(true); + mBus.send(message.createReply()); + mClient->removeRequest(ChannelRequest::create(mBus, + request.path(), QVariantMap(), + mRegistrar->accountFactory(), + mRegistrar->connectionFactory(), + mRegistrar->channelFactory(), + mRegistrar->contactFactory()), errorName, errorMessage); +} + +struct TELEPATHY_QT4_NO_EXPORT ClientRegistrar::Private +{ + Private(const QDBusConnection &bus, const AccountFactoryConstPtr &accFactory, + const ConnectionFactoryConstPtr &connFactory, const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) + : bus(bus), accFactory(accFactory), connFactory(connFactory), chanFactory(chanFactory), + contactFactory(contactFactory) + { + if (accFactory->dbusConnection().name() != bus.name()) { + warning() << " The D-Bus connection in the account factory is not the proxy connection"; + } + + if (connFactory->dbusConnection().name() != bus.name()) { + warning() << " The D-Bus connection in the connection factory is not the proxy connection"; + } + + if (chanFactory->dbusConnection().name() != bus.name()) { + warning() << " The D-Bus connection in the channel factory is not the proxy connection"; + } + } + + QDBusConnection bus; + + AccountFactoryConstPtr accFactory; + ConnectionFactoryConstPtr connFactory; + ChannelFactoryConstPtr chanFactory; + ContactFactoryConstPtr contactFactory; + + QHash<AbstractClientPtr, QString> clients; + QHash<AbstractClientPtr, QObject*> clientObjects; + QSet<QString> services; +}; + +/** + * \class ClientRegistrar + * \ingroup serverclient + * \headerfile TelepathyQt4/client-registrar.h <TelepathyQt4/ClientRegistrar> + * + * \brief The ClientRegistrar class is responsible for registering Telepathy + * clients (Observer, Approver, Handler). + * + * Clients should inherit AbstractClientObserver, AbstractClientApprover, + * AbstractClientHandler or some combination of these, by using multiple + * inheritance, and register themselves using registerClient(). + * + * See the individual classes descriptions for more details. + * + * \section cr_usage_sec Usage + * + * \subsection cr_create_sec Creating a client registrar object + * + * One way to create a ClientRegistrar object is to just call the create method. + * For example: + * + * \code ClientRegistrarPtr cr = ClientRegistrar::create(); \endcode + * + * You can also provide a D-Bus connection as a QDBusConnection: + * + * \code ClientRegistrarPtr cr = ClientRegistrar::create(QDBusConnection::systemBus()); \endcode + * + * \subsection cr_registering_sec Registering a client + * + * To register a client, just call registerClient() with a given AbstractClientPtr + * pointing to a valid AbstractClient instance. + * + * \code + * + * class MyClient : public AbstractClientObserver, public AbstractClientHandler + * { + * ... + * }; + * + * ... + * + * ClientRegistrarPtr cr = ClientRegistrar::create(); + * SharedPtr<MyClient> client = SharedPtr<MyClient>(new MyClient(...)); + * cr->registerClient(AbstractClientPtr::dynamicCast(client), "myclient"); + * + * \endcode + * + * \sa AbstractClientObserver, AbstractClientApprover, AbstractClientHandler + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Create a new client registrar object using the given \a bus. + * + * The instance will use an account factory creating Tp::Account objects with no features + * ready, a connection factory creating Tp::Connection objects with no features ready, and a channel + * factory creating stock Telepathy-Qt4 channel subclasses, as appropriate, with no features ready. + * + * \param bus QDBusConnection to use. + * \return A ClientRegistrarPtr object pointing to the newly created ClientRegistrar object. + */ +ClientRegistrarPtr ClientRegistrar::create(const QDBusConnection &bus) +{ + return create(bus, AccountFactory::create(bus), ConnectionFactory::create(bus), + ChannelFactory::create(bus), ContactFactory::create()); +} + +/** + * Create a new client registrar object using QDBusConnection::sessionBus() and the given factories. + * + * \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 ClientRegistrarPtr object pointing to the newly created ClientRegistrar object. + */ +ClientRegistrarPtr ClientRegistrar::create( + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return create(QDBusConnection::sessionBus(), accountFactory, connectionFactory, channelFactory, + contactFactory); +} + +/** + * Create a new client registrar object using the given \a bus and the given factories. + * + * \param bus QDBusConnection to use. + * \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 ClientRegistrarPtr object pointing to the newly created ClientRegistrar object. + */ +ClientRegistrarPtr ClientRegistrar::create(const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return ClientRegistrarPtr(new ClientRegistrar(bus, accountFactory, connectionFactory, + channelFactory, contactFactory)); +} + +/** + * Create a new client registrar object using the bus and factories of the given Account \a manager. + * + * Using this create method will enable (like any other way of passing the same factories to an AM + * and a registrar) getting the same Account/Connection etc. proxy instances from both + * AccountManager and AbstractClient implementations. + * + * \param manager The AccountManager the bus and factories of which should be used. + * \return A ClientRegistrarPtr object pointing to the newly ClientRegistrar object. + */ +ClientRegistrarPtr ClientRegistrar::create(const AccountManagerPtr &manager) +{ + if (!manager) { + return ClientRegistrarPtr(); + } + + return create(manager->dbusConnection(), manager->accountFactory(), + manager->connectionFactory(), manager->channelFactory(), manager->contactFactory()); +} + +/** + * Construct a new client registrar object using the given \a bus and the given factories. + * + * \param bus QDBusConnection to use. + * \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. + */ +ClientRegistrar::ClientRegistrar(const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) + : Object(), + mPriv(new Private(bus, accountFactory, connectionFactory, channelFactory, contactFactory)) +{ +} + +/** + * Class destructor. + */ +ClientRegistrar::~ClientRegistrar() +{ + unregisterClients(); + delete mPriv; +} + +/** + * Return the D-Bus connection being used by this client registrar. + * + * \return A QDBusConnection object. + */ +QDBusConnection ClientRegistrar::dbusConnection() const +{ + return mPriv->bus; +} + +/** + * Get the account factory used by this client registrar. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the registrar would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the AccountFactory object. + */ +AccountFactoryConstPtr ClientRegistrar::accountFactory() const +{ + return mPriv->accFactory; +} + +/** + * Get the connection factory used by this client registrar. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the registrar would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ConnectionFactory object. + */ +ConnectionFactoryConstPtr ClientRegistrar::connectionFactory() const +{ + return mPriv->connFactory; +} + +/** + * Get the channel factory used by this client registrar. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the registrar would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ChannelFactory object. + */ +ChannelFactoryConstPtr ClientRegistrar::channelFactory() const +{ + return mPriv->chanFactory; +} + +/** + * Get the contact factory used by this client registrar. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the registrar would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ContactFactory object. + */ +ContactFactoryConstPtr ClientRegistrar::contactFactory() const +{ + return mPriv->contactFactory; +} + +/** + * Return the list of clients registered using registerClient() on this client + * registrar. + * + * \return A list of pointers to AbstractClient objects. + * \sa registerClient(), unregisterClient() + */ +QList<AbstractClientPtr> ClientRegistrar::registeredClients() const +{ + return mPriv->clients.keys(); +} + +/** + * Register a client on D-Bus. + * + * The client registrar will export the appropriate D-Bus interfaces, + * based on the abstract classes subclassed by \param client. + * + * If each of a client instance should be able to manipulate channels + * separately, set unique to true. + * + * The client name MUST be a non-empty string of ASCII digits, letters, dots + * and/or underscores, starting with a letter, and without sets of + * two consecutive dots or a dot followed by a digit. + * + * This method will do nothing if the client is already registered, and \c true + * will be returned. + * + * To unregister a client use unregisterClient(). + * + * \param client The client to register. + * \param clientName The client name used to register. + * \param unique Whether each of a client instance is able to manipulate + * channels separately. + * \return \c true if \a client was successfully registered, \c false otherwise. + * \sa registeredClients(), unregisterClient() + */ +bool ClientRegistrar::registerClient(const AbstractClientPtr &client, + const QString &clientName, bool unique) +{ + if (!client) { + warning() << "Unable to register a null client"; + return false; + } + + if (mPriv->clients.contains(client)) { + debug() << "Client already registered"; + return true; + } + + QString busName = QLatin1String("org.freedesktop.Telepathy.Client."); + busName.append(clientName); + if (unique) { + // o.f.T.Client.clientName.<unique_bus_name>_<pointer> should be enough to identify + // an unique identifier + busName.append(QString(QLatin1String(".%1_%2")) + .arg(mPriv->bus.baseService() + .replace(QLatin1String(":"), QLatin1String("_")) + .replace(QLatin1String("."), QLatin1String("_"))) + .arg((intptr_t) client.data(), 0, 16)); + } + + if (mPriv->services.contains(busName) || + !mPriv->bus.registerService(busName)) { + warning() << "Unable to register client: busName" << + busName << "already registered"; + return false; + } + + QObject *object = new QObject(this); + QStringList interfaces; + + AbstractClientHandler *handler = + dynamic_cast<AbstractClientHandler*>(client.data()); + if (handler) { + // export o.f.T.Client.Handler + new ClientHandlerAdaptor(this, handler, object); + interfaces.append( + QLatin1String("org.freedesktop.Telepathy.Client.Handler")); + if (handler->wantsRequestNotification()) { + // export o.f.T.Client.Interface.Requests + new ClientHandlerRequestsAdaptor(this, handler, object); + interfaces.append( + QLatin1String( + "org.freedesktop.Telepathy.Client.Interface.Requests")); + } + } + + AbstractClientObserver *observer = + dynamic_cast<AbstractClientObserver*>(client.data()); + if (observer) { + // export o.f.T.Client.Observer + new ClientObserverAdaptor(this, observer, object); + interfaces.append( + QLatin1String("org.freedesktop.Telepathy.Client.Observer")); + } + + AbstractClientApprover *approver = + dynamic_cast<AbstractClientApprover*>(client.data()); + if (approver) { + // export o.f.T.Client.Approver + new ClientApproverAdaptor(this, approver, object); + interfaces.append( + QLatin1String("org.freedesktop.Telepathy.Client.Approver")); + } + + if (interfaces.isEmpty()) { + warning() << "Client does not implement any known interface"; + // cleanup + mPriv->bus.unregisterService(busName); + return false; + } + + // export o.f.T.Client interface + new ClientAdaptor(this, interfaces, object); + + QString objectPath = QString(QLatin1String("/%1")).arg(busName); + objectPath.replace(QLatin1String("."), QLatin1String("/")); + if (!mPriv->bus.registerObject(objectPath, object)) { + // this shouldn't happen, but let's make sure + warning() << "Unable to register client: objectPath" << + objectPath << "already registered"; + // cleanup + delete object; + mPriv->bus.unregisterService(busName); + return false; + } + + if (handler) { + handler->setRegistered(true); + } + + debug() << "Client registered - busName:" << busName << + "objectPath:" << objectPath << "interfaces:" << interfaces; + + mPriv->services.insert(busName); + mPriv->clients.insert(client, objectPath); + mPriv->clientObjects.insert(client, object); + + return true; +} + +/** + * Unregister a client registered using registerClient() on this client + * registrar. + * + * If \a client was not registered previously, \c false will be returned. + * + * \param client The client to unregister. + * \return \c true if \a client was successfully unregistered, \c false otherwise. + * \sa registeredClients(), registerClient() + */ +bool ClientRegistrar::unregisterClient(const AbstractClientPtr &client) +{ + if (!mPriv->clients.contains(client)) { + warning() << "Trying to unregister an unregistered client"; + return false; + } + + AbstractClientHandler *handler = + dynamic_cast<AbstractClientHandler*>(client.data()); + if (handler) { + handler->setRegistered(false); + } + + QString objectPath = mPriv->clients.value(client); + mPriv->bus.unregisterObject(objectPath); + mPriv->clients.remove(client); + QObject *object = mPriv->clientObjects.value(client); + // delete object here and it's children (adaptors), to make sure if adaptor + // is keeping a static list of adaptors per connection, the list is updated. + delete object; + mPriv->clientObjects.remove(client); + + QString busName = objectPath.mid(1).replace(QLatin1String("/"), + QLatin1String(".")); + mPriv->bus.unregisterService(busName); + mPriv->services.remove(busName); + + debug() << "Client unregistered - busName:" << busName << + "objectPath:" << objectPath; + + return true; +} + +/** + * Unregister all clients registered using registerClient() on this client + * registrar. + * + * \sa registeredClients(), registerClient(), unregisterClient() + */ +void ClientRegistrar::unregisterClients() +{ + // copy the hash as it will be modified + QHash<AbstractClientPtr, QString> clients = mPriv->clients; + + QHash<AbstractClientPtr, QString>::const_iterator end = + clients.constEnd(); + QHash<AbstractClientPtr, QString>::const_iterator it = + clients.constBegin(); + while (it != end) { + unregisterClient(it.key()); + ++it; + } +} + +} // Tp |