/**
* 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/_gen/readiness-helper.moc.hpp"
#include "TelepathyQt/debug-internal.h"
#include
#include
#include
#include
#include
#include
#include
#include
namespace Tp
{
struct TP_QT_NO_EXPORT ReadinessHelper::Introspectable::Private : public QSharedData
{
Private(const QSet &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 makesSenseForStatuses;
Features dependsOnFeatures;
QStringList dependsOnInterfaces;
IntrospectFunc introspectFunc;
void *introspectFuncData;
bool critical;
};
ReadinessHelper::Introspectable::Introspectable()
: mPriv(new Private(QSet(), Features(), QStringList(), nullptr, nullptr, false))
{
}
ReadinessHelper::Introspectable::Introspectable(const QSet &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 supportedStatuses;
Features supportedFeatures;
Features satisfiedFeatures;
Features requestedFeatures;
Features missingFeatures;
Features pendingFeatures;
Features inFlightFeatures;
QHash > missingFeaturesErrors;
QList pendingOperations;
bool pendingStatusChange;
uint pendingStatus;
};
ReadinessHelper::Private::Private(
ReadinessHelper *parent,
RefCounted *object,
uint currentStatus,
const Introspectables &introspectables)
: parent(parent),
object(object),
proxy(nullptr),
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 != nullptr);
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 != nullptr);
for (Introspectables::const_iterator i = introspectables.constBegin();
i != introspectables.constEnd(); ++i) {
Feature feature = i.key();
Introspectable introspectable = i.value();
Q_ASSERT(introspectable.mPriv->introspectFunc != nullptr);
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(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(TP_QT_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,
TP_QT_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
*
* \brief The ReadinessHelper class is a helper class used by the introspection
* process.
*/
/**
* \class ReadinessHelper::Introspectable
* \ingroup utils
* \headerfile TelepathyQt/readiness-helper.h
*
* \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 = TP_QT_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 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(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(mPriv->object),
requestedFeatures);
operation->setFinishedWithError(
TP_QT_ERROR_INVALID_ARGUMENT,
QLatin1String("Requested features contains unsupported feature"));
return operation;
}
if (mPriv->proxy && !mPriv->proxy->isValid()) {
PendingReady *operation = new PendingReady(SharedPtr(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(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