summaryrefslogtreecommitdiff
path: root/TelepathyQt/readiness-helper.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'TelepathyQt/readiness-helper.cpp')
-rw-r--r--TelepathyQt/readiness-helper.cpp684
1 files changed, 684 insertions, 0 deletions
diff --git a/TelepathyQt/readiness-helper.cpp b/TelepathyQt/readiness-helper.cpp
new file mode 100644
index 00000000..1bc810ab
--- /dev/null
+++ b/TelepathyQt/readiness-helper.cpp
@@ -0,0 +1,684 @@
+/**
+ * This file is part of TelepathyQt
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @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 <TelepathyQt/ReadinessHelper>
+
+#include "TelepathyQt/_gen/readiness-helper.moc.hpp"
+
+#include "TelepathyQt/debug-internal.h"
+
+#include <TelepathyQt/Constants>
+#include <TelepathyQt/DBusProxy>
+#include <TelepathyQt/PendingReady>
+#include <TelepathyQt/RefCounted>
+#include <TelepathyQt/SharedPtr>
+
+#include <QDBusError>
+#include <QSharedData>
+#include <QTimer>
+
+namespace Tp
+{
+
+struct TP_QT_NO_EXPORT ReadinessHelper::Introspectable::Private : public QSharedData
+{
+ Private(const QSet<uint> &makesSenseForStatuses,
+ const Features &dependsOnFeatures,
+ const QStringList &dependsOnInterfaces,
+ IntrospectFunc introspectFunc,
+ void *introspectFuncData,
+ bool critical)
+ : makesSenseForStatuses(makesSenseForStatuses),
+ dependsOnFeatures(dependsOnFeatures),
+ dependsOnInterfaces(dependsOnInterfaces),
+ introspectFunc(introspectFunc),
+ introspectFuncData(introspectFuncData),
+ critical(critical) {}
+
+ QSet<uint> makesSenseForStatuses;
+ Features dependsOnFeatures;
+ QStringList dependsOnInterfaces;
+ IntrospectFunc introspectFunc;
+ void *introspectFuncData;
+ bool critical;
+};
+
+ReadinessHelper::Introspectable::Introspectable()
+ : mPriv(new Private(QSet<uint>(), Features(), QStringList(), 0, 0, false))
+{
+}
+
+ReadinessHelper::Introspectable::Introspectable(const QSet<uint> &makesSenseForStatuses,
+ const Features &dependsOnFeatures, const QStringList &dependsOnInterfaces,
+ IntrospectFunc introspectFunc, void *introspectFuncData, bool critical)
+ : mPriv(new Private(makesSenseForStatuses, dependsOnFeatures, dependsOnInterfaces,
+ introspectFunc, introspectFuncData, critical))
+{
+}
+
+ReadinessHelper::Introspectable::Introspectable(const Introspectable &other)
+ : mPriv(other.mPriv)
+{
+}
+
+ReadinessHelper::Introspectable::~Introspectable()
+{
+}
+
+ReadinessHelper::Introspectable &ReadinessHelper::Introspectable::operator=(
+ const Introspectable &other)
+{
+ mPriv = other.mPriv;
+ return *this;
+}
+
+struct TP_QT_NO_EXPORT ReadinessHelper::Private
+{
+ Private(ReadinessHelper *parent,
+ RefCounted *object,
+ uint currentStatus,
+ const Introspectables &introspectables);
+ Private(ReadinessHelper *parent,
+ DBusProxy *proxy,
+ uint currentStatus,
+ const Introspectables &introspectables);
+ ~Private();
+
+ void setCurrentStatus(uint newStatus);
+ void setIntrospectCompleted(const Feature &feature, bool success,
+ const QString &errorName = QString(),
+ const QString &errorMessage = QString());
+ void iterateIntrospection();
+ Features depsFor(const Feature &feature); // Recursive dependencies for a feature
+
+ void abortOperations(const QString &errorName, const QString &errorMessage);
+
+ ReadinessHelper *parent;
+ RefCounted *object;
+ DBusProxy *proxy;
+ uint currentStatus;
+ QStringList interfaces;
+ Introspectables introspectables;
+ QSet<uint> supportedStatuses;
+ Features supportedFeatures;
+ Features satisfiedFeatures;
+ Features requestedFeatures;
+ Features missingFeatures;
+ Features pendingFeatures;
+ Features inFlightFeatures;
+ QHash<Feature, QPair<QString, QString> > missingFeaturesErrors;
+ QList<PendingReady *> pendingOperations;
+
+ bool pendingStatusChange;
+ uint pendingStatus;
+};
+
+ReadinessHelper::Private::Private(
+ ReadinessHelper *parent,
+ RefCounted *object,
+ uint currentStatus,
+ const Introspectables &introspectables)
+ : parent(parent),
+ object(object),
+ proxy(0),
+ currentStatus(currentStatus),
+ introspectables(introspectables),
+ pendingStatusChange(false),
+ pendingStatus(-1)
+{
+ for (Introspectables::const_iterator i = introspectables.constBegin();
+ i != introspectables.constEnd(); ++i) {
+ Feature feature = i.key();
+ Introspectable introspectable = i.value();
+ Q_ASSERT(introspectable.mPriv->introspectFunc != 0);
+ supportedStatuses += introspectable.mPriv->makesSenseForStatuses;
+ supportedFeatures += feature;
+ }
+}
+
+ReadinessHelper::Private::Private(
+ ReadinessHelper *parent,
+ DBusProxy *proxy,
+ uint currentStatus,
+ const Introspectables &introspectables)
+ : parent(parent),
+ object(proxy),
+ proxy(proxy),
+ currentStatus(currentStatus),
+ introspectables(introspectables),
+ pendingStatusChange(false),
+ pendingStatus(-1)
+{
+ Q_ASSERT(proxy != 0);
+
+ for (Introspectables::const_iterator i = introspectables.constBegin();
+ i != introspectables.constEnd(); ++i) {
+ Feature feature = i.key();
+ Introspectable introspectable = i.value();
+ Q_ASSERT(introspectable.mPriv->introspectFunc != 0);
+ supportedStatuses += introspectable.mPriv->makesSenseForStatuses;
+ supportedFeatures += feature;
+ }
+}
+
+ReadinessHelper::Private::~Private()
+{
+ const static QString messageDestroyed(QLatin1String("Destroyed"));
+
+ abortOperations(TP_QT_ERROR_CANCELLED, messageDestroyed);
+}
+
+void ReadinessHelper::Private::setCurrentStatus(uint newStatus)
+{
+ if (currentStatus == newStatus) {
+ return;
+ }
+
+ if (inFlightFeatures.isEmpty()) {
+ currentStatus = newStatus;
+ satisfiedFeatures.clear();
+ missingFeatures.clear();
+
+ // Make all features that were requested for the new status pending again
+ pendingFeatures = requestedFeatures;
+
+ // becomeReady ensures that the recursive dependencies of the requested features are already
+ // in the requested set, so we don't have to re-add them here
+
+ if (supportedStatuses.contains(currentStatus)) {
+ QTimer::singleShot(0, parent, SLOT(iterateIntrospection()));
+ } else {
+ emit parent->statusReady(currentStatus);
+ }
+ } else {
+ debug() << "status changed while introspection process was running";
+ pendingStatusChange = true;
+ pendingStatus = newStatus;
+ }
+}
+
+void ReadinessHelper::Private::setIntrospectCompleted(const Feature &feature,
+ bool success, const QString &errorName, const QString &errorMessage)
+{
+ debug() << "ReadinessHelper::setIntrospectCompleted: feature:" << feature <<
+ "- success:" << success;
+ if (pendingStatusChange) {
+ debug() << "ReadinessHelper::setIntrospectCompleted called while there is "
+ "a pending status change - ignoring";
+
+ inFlightFeatures.remove(feature);
+
+ // ignore all introspection completed as the state changed
+ if (!inFlightFeatures.isEmpty()) {
+ return;
+ }
+ pendingStatusChange = false;
+ setCurrentStatus(pendingStatus);
+ return;
+ }
+
+ Q_ASSERT(pendingFeatures.contains(feature));
+ Q_ASSERT(inFlightFeatures.contains(feature));
+
+ if (success) {
+ satisfiedFeatures.insert(feature);
+ }
+ else {
+ missingFeatures.insert(feature);
+ missingFeaturesErrors.insert(feature,
+ QPair<QString, QString>(errorName, errorMessage));
+ if (errorName.isEmpty()) {
+ warning() << "ReadinessHelper::setIntrospectCompleted: Feature" <<
+ feature << "introspection failed but no error message was given";
+ }
+ }
+
+ pendingFeatures.remove(feature);
+ inFlightFeatures.remove(feature);
+
+ QTimer::singleShot(0, parent, SLOT(iterateIntrospection()));
+}
+
+void ReadinessHelper::Private::iterateIntrospection()
+{
+ if (proxy && !proxy->isValid()) {
+ debug() << "ReadinessHelper: not iterating as the proxy is invalidated";
+ return;
+ }
+
+ // When there's a pending status change, we MUST NOT
+ // - finish PendingReadys (as they'd not be finished in the new status)
+ // - claim a status as being ready (because the new one isn't)
+ // and SHOULD NOT
+ // - fire new introspection jobs (as that would just delay the pending status change even more)
+ // and NEED NOT
+ // - flag features as missing (as the completed features will be cleared anyway when starting
+ // introspection for the new status)
+ //
+ // So we can safely skip the rest of this function here.
+ if (pendingStatusChange) {
+ debug() << "ReadinessHelper: not iterating as a status change is pending";
+ return;
+ }
+
+ // Flag the currently pending reverse dependencies of any previously discovered missing features
+ // as missing
+ foreach (const Feature &feature, pendingFeatures) {
+ if (!depsFor(feature).intersect(missingFeatures).isEmpty()) {
+ missingFeatures.insert(feature);
+ missingFeaturesErrors.insert(feature,
+ QPair<QString, QString>(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Feature depends on other features that are not available")));
+ }
+ }
+
+ const Features completedFeatures = satisfiedFeatures + missingFeatures;
+
+ // check if any pending operations for becomeReady should finish now
+ // based on their requested features having nothing more than what
+ // satisfiedFeatures + missingFeatures has
+ QString errorName;
+ QString errorMessage;
+ foreach (PendingReady *operation, pendingOperations) {
+ if ((operation->requestedFeatures() - completedFeatures).isEmpty()) {
+ if (parent->isReady(operation->requestedFeatures(), &errorName, &errorMessage)) {
+ operation->setFinished();
+ } else {
+ operation->setFinishedWithError(errorName, errorMessage);
+ }
+
+ // Remove the operation from tracking, so we don't double-finish it
+ //
+ // Qt foreach makes a copy of the container, which will be detached at this point, so
+ // this is perfectly safe
+ pendingOperations.removeOne(operation);
+ }
+ }
+
+ if ((requestedFeatures - completedFeatures).isEmpty()) {
+ // Otherwise, we'd emit statusReady with currentStatus although we are supposed to be
+ // introspecting the pendingStatus and only when that is complete, emit statusReady
+ Q_ASSERT(!pendingStatusChange);
+
+ // all requested features satisfied or missing
+ emit parent->statusReady(currentStatus);
+ return;
+ }
+
+ // update pendingFeatures with the difference of requested and
+ // satisfied + missing
+ pendingFeatures -= completedFeatures;
+
+ // find out which features don't have dependencies that are still pending
+ Features readyToIntrospect;
+ foreach (const Feature &feature, pendingFeatures) {
+ // missing doesn't have to be considered here anymore
+ if ((introspectables[feature].mPriv->dependsOnFeatures - satisfiedFeatures).isEmpty()) {
+ readyToIntrospect.insert(feature);
+ }
+ }
+
+ // now readyToIntrospect should contain all the features which have
+ // all their feature dependencies satisfied
+ foreach (const Feature &feature, readyToIntrospect) {
+ if (inFlightFeatures.contains(feature)) {
+ continue;
+ }
+
+ inFlightFeatures.insert(feature);
+
+ Introspectable introspectable = introspectables[feature];
+
+ if (!introspectable.mPriv->makesSenseForStatuses.contains(currentStatus)) {
+ // No-op satisfy features for which nothing has to be done in
+ // the current state
+ setIntrospectCompleted(feature, true);
+ return; // will be called with a single-shot soon again
+ }
+
+ foreach (const QString &interface, introspectable.mPriv->dependsOnInterfaces) {
+ if (!interfaces.contains(interface)) {
+ // If a feature is ready to introspect and depends on a interface
+ // that is not present the feature can't possibly be satisfied
+ debug() << "feature" << feature << "depends on interfaces" <<
+ introspectable.mPriv->dependsOnInterfaces << ", but interface" << interface <<
+ "is not present";
+ setIntrospectCompleted(feature, false,
+ QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Feature depend on interfaces that are not available"));
+ return; // will be called with a single-shot soon again
+ }
+ }
+
+ // yes, with the dependency info, we can even parallelize
+ // introspection of several features at once, reducing total round trip
+ // time considerably with many independent features!
+ (*(introspectable.mPriv->introspectFunc))(introspectable.mPriv->introspectFuncData);
+ }
+}
+
+Features ReadinessHelper::Private::depsFor(const Feature &feature)
+{
+ Features deps;
+
+ foreach (Feature dep, introspectables[feature].mPriv->dependsOnFeatures) {
+ deps += dep;
+ deps += depsFor(dep);
+ }
+
+ return deps;
+}
+
+void ReadinessHelper::Private::abortOperations(const QString &errorName,
+ const QString &errorMessage)
+{
+ foreach (PendingReady *operation, pendingOperations) {
+ operation->setFinishedWithError(errorName, errorMessage);
+ }
+ pendingOperations.clear();
+}
+
+/**
+ * \class ReadinessHelper
+ * \ingroup utils
+ * \headerfile TelepathyQt/readiness-helper.h <TelepathyQt/ReadinessHelper>
+ *
+ * \brief The ReadinessHelper class is a helper class used by the introspection
+ * process.
+ */
+
+/**
+ * \class ReadinessHelper::Introspectable
+ * \ingroup utils
+ * \headerfile TelepathyQt/readiness-helper.h <TelepathyQt/ReadinessHelper>
+ *
+ * \brief The ReadinessHelper::Introspectable class represents a introspectable
+ * used by ReadinessHelper.
+ */
+
+ReadinessHelper::ReadinessHelper(RefCounted *object,
+ uint currentStatus,
+ const Introspectables &introspectables,
+ QObject *parent)
+ : QObject(parent),
+ mPriv(new Private(this, object, currentStatus, introspectables))
+{
+}
+
+ReadinessHelper::ReadinessHelper(DBusProxy *proxy,
+ uint currentStatus,
+ const Introspectables &introspectables,
+ QObject *parent)
+ : QObject(parent),
+ mPriv(new Private(this, proxy, currentStatus, introspectables))
+{
+}
+
+ReadinessHelper::~ReadinessHelper()
+{
+ delete mPriv;
+}
+
+void ReadinessHelper::addIntrospectables(const Introspectables &introspectables)
+{
+ // QMap::unite will create multiple items if the key is already in the map
+ // so let's make sure we don't duplicate keys
+ for (Introspectables::const_iterator i = introspectables.constBegin();
+ i != introspectables.constEnd(); ++i) {
+ Feature feature = i.key();
+ if (mPriv->introspectables.contains(feature)) {
+ warning() << "ReadinessHelper::addIntrospectables: trying to add an "
+ "introspectable for feature" << feature << "but introspectable "
+ "for this feature already exists";
+ } else {
+ Introspectable introspectable = i.value();
+ mPriv->introspectables.insert(feature, introspectable);
+ mPriv->supportedStatuses += introspectable.mPriv->makesSenseForStatuses;
+ mPriv->supportedFeatures += feature;
+ }
+ }
+
+ debug() << "ReadinessHelper: new supportedStatuses =" << mPriv->supportedStatuses;
+ debug() << "ReadinessHelper: new supportedFeatures =" << mPriv->supportedFeatures;
+}
+
+uint ReadinessHelper::currentStatus() const
+{
+ return mPriv->currentStatus;
+}
+
+void ReadinessHelper::setCurrentStatus(uint currentStatus)
+{
+ mPriv->setCurrentStatus(currentStatus);
+}
+
+/**
+ * Force the current internal status to \a currentStatus.
+ *
+ * Note that this method will not start a new introspection or restart the
+ * current one in case one is running.
+ *
+ * This is useful for example when the status is unknown initially but it will
+ * become known in the first introspection run and there is no need to re-run
+ * the introspection.
+ *
+ * \param currentStatus The status to set.
+ */
+void ReadinessHelper::forceCurrentStatus(uint currentStatus)
+{
+ mPriv->currentStatus = currentStatus;
+}
+
+QStringList ReadinessHelper::interfaces() const
+{
+ return mPriv->interfaces;
+}
+
+void ReadinessHelper::setInterfaces(const QStringList &interfaces)
+{
+ mPriv->interfaces = interfaces;
+}
+
+Features ReadinessHelper::requestedFeatures() const
+{
+ return mPriv->requestedFeatures;
+}
+
+Features ReadinessHelper::actualFeatures() const
+{
+ return mPriv->satisfiedFeatures;
+}
+
+Features ReadinessHelper::missingFeatures() const
+{
+ return mPriv->missingFeatures;
+}
+
+bool ReadinessHelper::isReady(const Feature &feature,
+ QString *errorName, QString *errorMessage) const
+{
+ if (mPriv->proxy && !mPriv->proxy->isValid()) {
+ if (errorName) {
+ *errorName = mPriv->proxy->invalidationReason();
+ }
+ if (errorMessage) {
+ *errorMessage = mPriv->proxy->invalidationMessage();
+ }
+ return false;
+ }
+
+ if (!mPriv->supportedFeatures.contains(feature)) {
+ if (errorName) {
+ *errorName = QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT);
+ }
+ if (errorMessage) {
+ *errorMessage = QLatin1String("Unsupported feature");
+ }
+ return false;
+ }
+
+ bool ret = true;
+
+ if (feature.isCritical()) {
+ if (!mPriv->satisfiedFeatures.contains(feature)) {
+ ret = false;
+ }
+ } else {
+ if (!mPriv->satisfiedFeatures.contains(feature) &&
+ !mPriv->missingFeatures.contains(feature)) {
+ ret = false;
+ }
+ }
+
+ if (!ret) {
+ QPair<QString, QString> error = mPriv->missingFeaturesErrors[feature];
+ if (errorName) {
+ *errorName = error.first;
+ }
+ if (errorMessage) {
+ *errorMessage = error.second;
+ }
+ }
+
+ return ret;
+}
+
+bool ReadinessHelper::isReady(const Features &features, QString *errorName, QString *errorMessage) const
+{
+ if (mPriv->proxy && !mPriv->proxy->isValid()) {
+ if (errorName) {
+ *errorName = mPriv->proxy->invalidationReason();
+ }
+ if (errorMessage) {
+ *errorMessage = mPriv->proxy->invalidationMessage();
+ }
+ return false;
+ }
+
+ Q_ASSERT(!features.isEmpty());
+
+ foreach (const Feature &feature, features) {
+ if (!isReady(feature, errorName, errorMessage)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+PendingReady *ReadinessHelper::becomeReady(const Features &requestedFeatures)
+{
+ Q_ASSERT(!requestedFeatures.isEmpty());
+
+ if (mPriv->proxy) {
+ connect(mPriv->proxy,
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ this,
+ SLOT(onProxyInvalidated(Tp::DBusProxy*,QString,QString)),
+ Qt::UniqueConnection);
+
+ if (!mPriv->proxy->isValid()) {
+ PendingReady *operation = new PendingReady(SharedPtr<RefCounted>(mPriv->object),
+ requestedFeatures);
+ operation->setFinishedWithError(mPriv->proxy->invalidationReason(),
+ mPriv->proxy->invalidationMessage());
+ return operation;
+ }
+ }
+
+ Features supportedFeatures = mPriv->supportedFeatures;
+ if (supportedFeatures.intersect(requestedFeatures) != requestedFeatures) {
+ warning() << "ReadinessHelper::becomeReady called with invalid features: requestedFeatures =" <<
+ requestedFeatures << "- supportedFeatures =" << mPriv->supportedFeatures;
+ PendingReady *operation = new PendingReady(SharedPtr<RefCounted>(mPriv->object),
+ requestedFeatures);
+ operation->setFinishedWithError(
+ QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Requested features contains unsupported feature"));
+ return operation;
+ }
+
+ if (mPriv->proxy && !mPriv->proxy->isValid()) {
+ PendingReady *operation = new PendingReady(SharedPtr<RefCounted>(mPriv->object),
+ requestedFeatures);
+ operation->setFinishedWithError(mPriv->proxy->invalidationReason(),
+ mPriv->proxy->invalidationMessage());
+ return operation;
+ }
+
+ PendingReady *operation;
+ foreach (operation, mPriv->pendingOperations) {
+ if (operation->requestedFeatures() == requestedFeatures) {
+ return operation;
+ }
+ }
+
+ // Insert the dependencies of the requested features too
+ Features requestedWithDeps = requestedFeatures;
+ foreach (const Feature &feature, requestedFeatures) {
+ requestedWithDeps.unite(mPriv->depsFor(feature));
+ }
+
+ mPriv->requestedFeatures += requestedWithDeps;
+ mPriv->pendingFeatures += requestedWithDeps; // will be updated in iterateIntrospection
+
+ operation = new PendingReady(SharedPtr<RefCounted>(mPriv->object), requestedFeatures);
+ mPriv->pendingOperations.append(operation);
+ // Only we finish these PendingReadys, so we don't need destroyed or finished handling for them
+ // - we already know when that happens, as we caused it!
+
+ QTimer::singleShot(0, this, SLOT(iterateIntrospection()));
+
+ return operation;
+}
+
+void ReadinessHelper::setIntrospectCompleted(const Feature &feature, bool success,
+ const QString &errorName, const QString &errorMessage)
+{
+ if (mPriv->proxy && !mPriv->proxy->isValid()) {
+ // proxy became invalid, ignore here
+ return;
+ }
+ mPriv->setIntrospectCompleted(feature, success, errorName, errorMessage);
+}
+
+void ReadinessHelper::setIntrospectCompleted(const Feature &feature, bool success,
+ const QDBusError &error)
+{
+ setIntrospectCompleted(feature, success, error.name(), error.message());
+}
+
+void ReadinessHelper::iterateIntrospection()
+{
+ mPriv->iterateIntrospection();
+}
+
+void ReadinessHelper::onProxyInvalidated(DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage)
+{
+ // clear satisfied and missing features as we have public methods to get them
+ mPriv->satisfiedFeatures.clear();
+ mPriv->missingFeatures.clear();
+
+ mPriv->abortOperations(errorName, errorMessage);
+}
+
+} // Tp