Commit 4cf5ef3c authored by Volker Krause's avatar Volker Krause
Browse files

Query agent status information asynchronously and answer all queries

from cached values. This massively reduces the risk of a misbehaving
agents blocking the Akonadi server and thus all clients. This also
removes the restriction that agents were not allowed to access the
agent manager themselves to avoid deadlocks.

svn path=/trunk/kdesupport/akonadi/; revision=881636
parent 1a1968bf
......@@ -31,6 +31,11 @@ if(NOT XSLTPROC_EXECUTABLE)
message(FATAL_ERROR "\nThe command line XSLT processor program 'xsltproc' could not be found.\nPlease install xsltproc.\n")
endif(NOT XSLTPROC_EXECUTABLE)
find_package(Boost REQUIRED)
if(NOT Boost_FOUND)
message(FATAL_ERROR "Akonadi requires the Boost C++ libraries.")
endif(NOT Boost_FOUND)
# this one actually sets only install locations
include(InstallSettings)
......
......@@ -14,6 +14,9 @@
<signal name="error">
<arg name="message" type="s" direction="out"/>
</signal>
<signal name="onlineChanged">
<arg name="state" type="b" direction="out"/>
</signal>
<method name="status">
<arg type="i" direction="out"/>
</method>
......@@ -28,6 +31,7 @@
</method>
<method name="setOnline">
<arg name="state" type="b" direction="in"/>
<annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
</method>
</interface>
</node>
......@@ -35,6 +35,10 @@
<arg name="agentIdentifier" type="s" direction="out"/>
<arg name="message" type="s" direction="out"/>
</signal>
<signal name="agentInstanceOnlineChanged">
<arg name="agentIdentifier" type="s" direction="out"/>
<arg name="state" type="b" direction="out"/>
</signal>
<method name="agentTypes">
<arg type="as" direction="out"/>
......
......@@ -3,6 +3,7 @@ include_directories( BEFORE
${QT_QTDBUS_INCLUDE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${Boost_INCLUDE_DIR}
)
#find_library( AKONADI_PROTOCOLINTERNALS_LIBRARY NAMES akonadiprotocolinternals
......@@ -19,6 +20,7 @@ set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${_ENABLE_EXCEPTIONS}" )
set( control_SRCS
${AKONADI_SHARED_SOURCES}
agentinfo.cpp
agentinstance.cpp
agentmanager.cpp
controlmanager.cpp
main.cpp
......
......@@ -81,29 +81,3 @@ void AgentInfo::save( QSettings *config ) const
config->setValue( QString::fromLatin1( "InstanceCounters/%1/InstanceCounter" ).arg( identifier ), instanceCounter );
}
}
AgentInstanceInfo::AgentInstanceInfo() :
controller( 0 ),
agentControlInterface( 0 ),
agentStatusInterface( 0 ),
resourceInterface( 0 )
{
}
bool AgentInstanceInfo::start( const AgentInfo &agentInfo, AgentManager *manager )
{
Q_ASSERT( !identifier.isEmpty() );
if ( identifier.isEmpty() )
return false;
const QString executable = Akonadi::XdgBaseDirs::findExecutableFile( agentInfo.exec );
if ( executable.isEmpty() ) {
manager->tracer()->error( QLatin1String( "AgentInstanceInfo::start" ),
QString::fromLatin1( "Unable to find agent executable '%1'" ).arg( agentInfo.exec ) );
return false;
}
controller = new Akonadi::ProcessControl( manager );
QStringList arguments;
arguments << "--identifier" << identifier;
controller->start( executable, arguments );
return true;
}
......@@ -56,20 +56,4 @@ class AgentInfo
static QLatin1String CapabilityAutostart;
};
class AgentInstanceInfo
{
public:
AgentInstanceInfo();
bool start( const AgentInfo &agentInfo, AgentManager* manager );
bool isResource() const { return resourceInterface; }
QString identifier;
QString agentType;
Akonadi::ProcessControl *controller;
org::freedesktop::Akonadi::Agent::Control *agentControlInterface;
org::freedesktop::Akonadi::Agent::Status *agentStatusInterface;
org::freedesktop::Akonadi::Resource *resourceInterface;
};
#endif
/*
Copyright (c) 2008 Volker Krause <vkrause@kde.org>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 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 Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "agentinstance.h"
#include "agentinfo.h"
#include "agentmanager.h"
#include "../../libs/xdgbasedirs_p.h"
#include "processcontrol.h"
AgentInstance::AgentInstance(AgentManager * manager) :
QObject( manager ),
mManager( manager ),
mController( 0 ),
mAgentControlInterface( 0 ),
mAgentStatusInterface( 0 ),
mResourceInterface( 0 ),
mStatus( 0 ),
mPercent( 0 ),
mOnline( false )
{
}
bool AgentInstance::start( const AgentInfo &agentInfo )
{
Q_ASSERT( !mIdentifier.isEmpty() );
if ( mIdentifier.isEmpty() )
return false;
mType = agentInfo.identifier;
const QString executable = Akonadi::XdgBaseDirs::findExecutableFile( agentInfo.exec );
if ( executable.isEmpty() ) {
mManager->tracer()->error( QLatin1String( "AgentInstanceInfo::start" ),
QString::fromLatin1( "Unable to find agent executable '%1'" ).arg( agentInfo.exec ) );
return false;
}
mController = new Akonadi::ProcessControl( this );
QStringList arguments;
arguments << "--identifier" << mIdentifier;
mController->start( executable, arguments );
return true;
}
void AgentInstance::quit()
{
mController->setCrashPolicy( Akonadi::ProcessControl::StopOnCrash );
if ( mAgentControlInterface && mAgentControlInterface->isValid() )
mAgentControlInterface->quit();
}
void AgentInstance::cleanup()
{
mController->setCrashPolicy( Akonadi::ProcessControl::StopOnCrash );
if ( mAgentControlInterface && mAgentControlInterface->isValid() )
mAgentControlInterface->cleanup();
}
bool AgentInstance::obtainAgentInterface()
{
delete mAgentControlInterface;
delete mAgentStatusInterface;
mAgentControlInterface = 0;
mAgentStatusInterface = 0;
org::freedesktop::Akonadi::Agent::Control *agentControlIface =
new org::freedesktop::Akonadi::Agent::Control( "org.freedesktop.Akonadi.Agent." + mIdentifier,
"/", QDBusConnection::sessionBus(), this );
if ( !agentControlIface || !agentControlIface->isValid() ) {
mManager->tracer()->error( QLatin1String( "AgentInstance::obtainAgentInterface" ),
QString( "Cannot connect to agent instance with identifier '%1', error message: '%2'" )
.arg( mIdentifier, agentControlIface ? agentControlIface->lastError().message() : "" ) );
return false;
}
mAgentControlInterface = agentControlIface;
org::freedesktop::Akonadi::Agent::Status *agentStatusIface =
new org::freedesktop::Akonadi::Agent::Status( "org.freedesktop.Akonadi.Agent." + mIdentifier,
"/", QDBusConnection::sessionBus(), this );
if ( !agentStatusIface || !agentStatusIface->isValid() ) {
mManager->tracer()->error( QLatin1String( "AgentInstance::obtainAgentInterface" ),
QString( "Cannot connect to agent instance with identifier '%1', error message: '%2'" )
.arg( mIdentifier, agentStatusIface ? agentStatusIface->lastError().message() : "" ) );
return false;
}
mAgentStatusInterface = agentStatusIface;
connect( agentStatusIface, SIGNAL(status(int,QString)), SLOT(statusChanged(int,QString)) );
connect( agentStatusIface, SIGNAL(percent(int)), SLOT(percentChanged(int)) );
connect( agentStatusIface, SIGNAL(warning(QString)), SLOT(warning(QString)) );
connect( agentStatusIface, SIGNAL(error(QString)), SLOT(error(QString)) );
connect( agentStatusIface, SIGNAL(onlineChanged(bool)), SLOT(onlineChanged(bool)) );
refreshAgentStatus();
return true;
}
bool AgentInstance::obtainResourceInterface()
{
delete mResourceInterface;
mResourceInterface = 0;
org::freedesktop::Akonadi::Resource *resInterface =
new org::freedesktop::Akonadi::Resource( "org.freedesktop.Akonadi.Resource." + mIdentifier,
"/", QDBusConnection::sessionBus(), this );
if ( !resInterface || !resInterface->isValid() ) {
mManager->tracer()->error( QLatin1String( "AgentInstance::obtainResourceInterface" ),
QString( "Cannot connect to agent instance with identifier '%1', error message: '%2'" )
.arg( mIdentifier, resInterface ? resInterface->lastError().message() : "" ) );
return false;
}
connect( resInterface, SIGNAL(nameChanged(QString)), SLOT(resourceNameChanged(QString)) );
mResourceInterface = resInterface;
refreshResourceStatus();
return true;
}
void AgentInstance::statusChanged(int status, const QString & statusMsg)
{
if ( mStatus == status && mStatusMessage == statusMsg )
return;
mStatus = status;
mStatusMessage = statusMsg;
emit mManager->agentInstanceStatusChanged( mIdentifier, mStatus, mStatusMessage );
}
void AgentInstance::statusStateChanged(int status)
{
statusChanged( status, mStatusMessage );
}
void AgentInstance::statusMessageChanged(const QString & msg)
{
statusChanged( mStatus, msg );
}
void AgentInstance::percentChanged(int percent)
{
if ( mPercent == percent )
return;
mPercent = percent;
emit mManager->agentInstanceProgressChanged( mIdentifier, mPercent, QString() );
}
void AgentInstance::warning(const QString & msg)
{
emit mManager->agentInstanceWarning( mIdentifier, msg );
}
void AgentInstance::error(const QString & msg)
{
emit mManager->agentInstanceError( mIdentifier, msg );
}
void AgentInstance::onlineChanged(bool state)
{
if ( mOnline == state )
return;
mOnline = state;
emit mManager->agentInstanceOnlineChanged( mIdentifier, state );
}
void AgentInstance::resourceNameChanged(const QString & name)
{
if ( name == mResourceName )
return;
mResourceName = name;
emit mManager->agentInstanceNameChanged( mIdentifier, name );
}
void AgentInstance::refreshAgentStatus()
{
if ( !hasAgentInterface() )
return;
// async calls so we are not blocked by misbehaving agents
mAgentStatusInterface->callWithCallback( QLatin1String("status"), QList<QVariant>(),
this, SLOT(statusStateChanged(int)),
SLOT(errorHandler(QDBusError)) );
mAgentStatusInterface->callWithCallback( QLatin1String("statusMessage"), QList<QVariant>(),
this, SLOT(statusMessageChanged(QString)),
SLOT(errorHandler(QDBusError)) );
mAgentStatusInterface->callWithCallback( QLatin1String("progress"), QList<QVariant>(),
this, SLOT(percentChanged(int)),
SLOT(errorHandler(QDBusError)) );
mAgentStatusInterface->callWithCallback( QLatin1String("isOnline"), QList<QVariant>(),
this, SLOT(onlineChanged(bool)),
SLOT(errorHandler(QDBusError)) );
}
void AgentInstance::refreshResourceStatus()
{
if ( !hasResourceInterface() )
return;
// async call so we are not blocked by misbehaving resources
mResourceInterface->callWithCallback( QLatin1String("name"), QList<QVariant>(),
this, SLOT(resourceNameChanged(QString)),
SLOT(errorHandler(QDBusError)) );
}
void AgentInstance::errorHandler(const QDBusError & error)
{
mManager->tracer()->error( QLatin1String( "AgentInstance::errorHandler" ),
QString( "D-Bus communication error '%1': '%2'" )
.arg( error.name(), error.message() ) );
// TODO try again after some time, esp. on timeout errors
}
#include "agentinstance.moc"
/*
Copyright (c) 2008 Volker Krause <vkrause@kde.org>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 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 Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#ifndef AKONADICONTROL_AGENTINSTANCE_H
#define AKONADICONTROL_AGENTINSTANCE_H
#include "controlinterface.h"
#include "statusinterface.h"
#include "resourceinterface.h"
#include "tracerinterface.h"
#include <QDBusError>
#include <QString>
#include <QStringList>
#include <boost/shared_ptr.hpp>
namespace Akonadi {
class ProcessControl;
}
class AgentManager;
class AgentInfo;
/**
* Represents one agent instance and takes care of communication with it.
*/
class AgentInstance : public QObject
{
Q_OBJECT
public:
typedef boost::shared_ptr<AgentInstance> Ptr;
explicit AgentInstance( AgentManager *manager );
QString identifier() const { return mIdentifier; }
void setIdentifier( const QString &identifier ) { mIdentifier = identifier; }
QString agentType() const { return mType; }
int status() const { return mStatus; }
QString statusMessage() const { return mStatusMessage; }
int progress() const { return mPercent; }
bool isOnline() const { return mOnline; }
QString resourceName() const { return mResourceName; }
bool start( const AgentInfo &agentInfo );
void quit();
void cleanup();
bool hasResourceInterface() const { return mResourceInterface; }
bool hasAgentInterface() const { return mAgentControlInterface && mAgentStatusInterface; }
org::freedesktop::Akonadi::Agent::Control* controlInterface() const { return mAgentControlInterface; }
org::freedesktop::Akonadi::Agent::Status* statusInterface() const { return mAgentStatusInterface; }
org::freedesktop::Akonadi::Resource* resourceInterface() const { return mResourceInterface; }
bool obtainAgentInterface();
bool obtainResourceInterface();
private slots:
void statusChanged( int status, const QString &statusMsg );
void statusStateChanged( int status );
void statusMessageChanged( const QString &msg );
void percentChanged( int percent );
void warning( const QString &msg );
void error( const QString &msg );
void onlineChanged( bool state );
void resourceNameChanged( const QString &name );
void refreshAgentStatus();
void refreshResourceStatus();
void errorHandler( const QDBusError &error );
private:
QString mIdentifier;
QString mType;
AgentManager *mManager;
Akonadi::ProcessControl *mController;
org::freedesktop::Akonadi::Agent::Control *mAgentControlInterface;
org::freedesktop::Akonadi::Agent::Status *mAgentStatusInterface;
org::freedesktop::Akonadi::Resource *mResourceInterface;
int mStatus;
QString mStatusMessage;
int mPercent;
QString mResourceName;
bool mOnline;
};
#endif
......@@ -90,11 +90,8 @@ AgentManager::~AgentManager()
void AgentManager::cleanup()
{
foreach ( const AgentInstanceInfo &info, mAgentInstances ) {
info.controller->setCrashPolicy( ProcessControl::StopOnCrash );
if ( info.agentControlInterface && info.agentControlInterface->isValid() )
info.agentControlInterface->quit();
}
foreach ( const AgentInstance::Ptr &inst, mAgentInstances )
inst->quit();
mAgentInstances.clear();
......@@ -110,8 +107,6 @@ void AgentManager::cleanup()
QStringList AgentManager::agentTypes() const
{
if ( !checkDBusDeadlock() )
return QStringList();
return mAgents.keys();
}
......@@ -161,25 +156,24 @@ QString AgentManager::createAgentInstance( const QString &identifier )
mAgents[ identifier ].instanceCounter++;
AgentInstanceInfo instance;
AgentInstance::Ptr instance( new AgentInstance( this ) );
if ( agentInfo.capabilities.contains( AgentInfo::CapabilityUnique ) )
instance.identifier = identifier;
instance->setIdentifier( identifier );
else
instance.identifier = QString::fromLatin1( "%1_%2" ).arg( identifier, QString::number( agentInfo.instanceCounter ) );
instance.agentType = identifier;
instance->setIdentifier( QString::fromLatin1( "%1_%2" ).arg( identifier, QString::number( agentInfo.instanceCounter ) ) );
if ( mAgentInstances.contains( instance.identifier ) ) {
if ( mAgentInstances.contains( instance->identifier() ) ) {
mTracer->warning( QLatin1String("AgentManager::createAgentInstance"),
QString::fromLatin1( "Cannot create another instance of agent '%1'." ).arg( identifier ) );
return QString();
}
if ( !instance.start( agentInfo, this ) )
if ( !instance->start( agentInfo ) )
return QString();
mAgentInstances.insert( instance.identifier, instance );
mAgentInstances.insert( instance->identifier(), instance );
save();
return instance.identifier;
return instance->identifier();
}
void AgentManager::removeAgentInstance( const QString &identifier )
......@@ -190,19 +184,14 @@ void AgentManager::removeAgentInstance( const QString &identifier )
return;
}
AgentInstanceInfo instance = mAgentInstances.value( identifier );
if ( instance.agentControlInterface ) {
instance.agentControlInterface->cleanup();
AgentInstance::Ptr instance = mAgentInstances.value( identifier );
if ( instance->hasAgentInterface() ) {
instance->cleanup();
} else {
mTracer->error( QLatin1String( "AgentManager::removeAgentInstance" ),
QString( "Agent instance '%1' has no interface!" ).arg( identifier ) );
}
delete instance.resourceInterface;
delete instance.agentControlInterface;
delete instance.agentStatusInterface;
delete instance.controller;
mAgentInstances.remove( identifier );
save();
......@@ -218,35 +207,33 @@ QString AgentManager::agentInstanceType( const QString &identifier )
return QString();
}
return mAgentInstances.value( identifier ).agentType;
return mAgentInstances.value( identifier )->agentType();
}
QStringList AgentManager::agentInstances() const
{
if ( !checkDBusDeadlock() )
return QStringList();
return mAgentInstances.keys();
}
int AgentManager::agentInstanceStatus( const QString &identifier ) const
{
if ( !checkAgentInterfaces( identifier, QLatin1String( "agentInstanceStatus" ) ) )
if ( !checkInstance( identifier ) )
return 2;
return mAgentInstances.value( identifier ).agentStatusInterface->status();
return mAgentInstances.value( identifier )->status();
}
QString AgentManager::agentInstanceStatusMessage( const QString &identifier ) const
{
if ( !checkAgentInterfaces( identifier, QLatin1String( "agentInstanceStatusMessage" ) ) )
if ( !checkInstance( identifier ) )
return QString();
return mAgentInstances.value( identifier ).agentStatusInterface->statusMessage();
return mAgentInstances.value( identifier )->statusMessage();
}
uint AgentManager::agentInstanceProgress( const QString &identifier ) const
{
if ( !checkAgentInterfaces( identifier, QLatin1String( "agentInstanceProgress" ) ) )
if ( !checkInstance( identifier ) )
return 0;
return mAgentInstances.value( identifier ).agentStatusInterface->progress();
return mAgentInstances.value( identifier )->progress();
}
QString AgentManager::agentInstanceProgressMessage( const QString &identifier ) const
......@@ -258,21 +245,21 @@ void AgentManager::agentInstanceConfigure( const QString &identifier, qlonglong
{
if ( !checkAgentInterfaces( identifier, "agentInstanceConfigure" ) )
return;
mAgentInstances.value( identifier ).agentControlInterface->configure( windowId );
mAgentInstances.value( identifier )->controlInterface()->configure( windowId );
}
bool AgentManager::agentInstanceOnline(const QString & identifier)
{
if ( !checkAgentInterfaces( identifier, QLatin1String( "agentInstanceOnline" ) ) )
if ( !checkInstance( identifier ) )
return false;
return mAgentInstances.value( identifier ).agentStatusInterface->isOnline();
return mAgentInstances.value( identifier )->isOnline();
}
void AgentManager::setAgentInstanceOnline(const QString & identifier, bool state )
{
if ( !checkAgentInterfaces( identifier, QLatin1String( "setAgentInstanceOnline" ) ) )
return;
mAgentInstances.value( identifier ).agentStatusInterface->setOnline( state );
mAgentInstances.value( identifier )->statusInterface()->setOnline( state );
}
// resource specific methods //
......@@ -280,40 +267,40 @@ void AgentManager::setAgentInstanceName( const QString &identifier, const QStrin
{
if ( !checkResourceInterface( identifier, QLatin1String( "setAgentInstanceName" ) ) )
return;
mAgentInstances.value( identifier ).resourceInterface->setName( name );
mAgentInstances.value( identifier )->resourceInterface()->setName( name );
}
QString AgentManager::agentInstanceName( const QString &identifier ) const
{
if ( !checkInstance( identifier ) )
return QString();
const AgentInstanceInfo inst = mAgentInstances.value( identifier );