20 #include "selftestdialog_p.h"
21 #include "agentmanager.h"
22 #include "dbusconnectionpool.h"
23 #include "session_p.h"
24 #include "servermanager.h"
25 #include "servermanager_p.h"
27 #include <akonadi/private/xdgbasedirs_p.h>
32 #include <KFileDialog>
33 #include <KLocalizedString>
34 #include <KMessageBox>
36 #include <KStandardDirs>
39 #include <QtCore/QFileInfo>
40 #include <QtCore/QProcess>
41 #include <QtCore/QSettings>
42 #include <QtCore/QTextStream>
43 #include <QtDBus/QtDBus>
44 #include <QApplication>
46 #include <QStandardItemModel>
47 #include <QtSql/QSqlDatabase>
48 #include <QtSql/QSqlError>
52 using namespace Akonadi;
54 static QString makeLink(
const QString &file )
56 return QString::fromLatin1(
"<a href=\"%1\">%2</a>" ).arg( file, file );
60 ResultTypeRole = Qt::UserRole,
71 setCaption( i18n(
"Akonadi Server Self-Test" ) );
72 setButtons( Close | User1 | User2 );
73 setButtonText( User1, i18n(
"Save Report..." ) );
74 setButtonIcon( User1, KIcon( QString::fromLatin1(
"document-save" ) ) );
75 setButtonText( User2, i18n(
"Copy Report to Clipboard" ) );
76 setButtonIcon( User2, KIcon( QString::fromLatin1(
"edit-copy" ) ) );
77 showButtonSeparator(
true );
78 ui.setupUi( mainWidget() );
80 mTestModel =
new QStandardItemModel(
this );
81 ui.testView->setModel( mTestModel );
82 connect( ui.testView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
83 SLOT(selectionChanged(QModelIndex)) );
84 connect( ui.detailsLabel, SIGNAL(linkActivated(QString)), SLOT(linkActivated(QString)) );
86 connect(
this, SIGNAL(user1Clicked()), SLOT(saveReport()) );
87 connect(
this, SIGNAL(user2Clicked()), SLOT(copyReport()) );
95 ui.introductionLabel->hide();
98 QStandardItem* SelfTestDialog::report( ResultType type,
const KLocalizedString & summary,
const KLocalizedString & details)
100 QStandardItem *item =
new QStandardItem( summary.toString() );
103 item->setIcon( KIcon( QString::fromLatin1(
"dialog-ok" ) ) );
106 item->setIcon( KIcon( QString::fromLatin1(
"dialog-ok-apply" ) ) );
109 item->setIcon( KIcon( QString::fromLatin1(
"dialog-warning" ) ) );
113 item->setIcon( KIcon( QString::fromLatin1(
"dialog-error" ) ) );
115 item->setEditable(
false );
116 item->setWhatsThis( details.toString() );
117 item->setData( type, ResultTypeRole );
118 item->setData( summary.toString( 0 ), SummaryRole );
119 item->setData( details.toString( 0 ), DetailsRole );
120 mTestModel->appendRow( item );
124 void SelfTestDialog::selectionChanged(
const QModelIndex &index )
126 if ( index.isValid() ) {
127 ui.detailsLabel->setText( index.data( Qt::WhatsThisRole ).toString() );
128 ui.detailsGroup->setEnabled(
true );
130 ui.detailsLabel->setText( QString() );
131 ui.detailsGroup->setEnabled(
false );
135 void SelfTestDialog::runTests()
139 const QString driver = serverSetting( QLatin1String(
"General" ),
"Driver", QLatin1String(
"QMYSQL" ) ).toString();
141 if (driver == QLatin1String(
"QPSQL" )) {
149 testMySQLServerLog();
150 testMySQLServerConfig();
154 testProtocolVersion();
160 QVariant SelfTestDialog::serverSetting(
const QString & group,
const char *key,
const QVariant &def )
const
162 const QString serverConfigFile = XdgBaseDirs::akonadiServerConfigFile( XdgBaseDirs::ReadWrite );
163 QSettings settings( serverConfigFile, QSettings::IniFormat );
164 settings.beginGroup( group );
165 return settings.value( QString::fromLatin1(key), def );
168 bool SelfTestDialog::useStandaloneMysqlServer()
const
170 const QString driver = serverSetting( QLatin1String(
"General" ),
"Driver", QLatin1String(
"QMYSQL" ) ).toString();
171 if ( driver != QLatin1String(
"QMYSQL" ) )
173 const bool startServer = serverSetting( driver,
"StartServer",
true ).toBool();
179 bool SelfTestDialog::runProcess(
const QString & app,
const QStringList & args, QString & result)
const
182 proc.start( app, args );
183 const bool rv = proc.waitForFinished();
185 result += QString::fromLocal8Bit( proc.readAllStandardError() );
186 result += QString::fromLocal8Bit( proc.readAllStandardOutput() );
190 void SelfTestDialog::testSQLDriver()
192 const QString driver = serverSetting( QLatin1String(
"General" ),
"Driver", QLatin1String(
"QMYSQL" ) ).toString();
193 const QStringList availableDrivers = QSqlDatabase::drivers();
194 const KLocalizedString detailsOk = ki18n(
"The QtSQL driver '%1' is required by your current Akonadi server configuration and was found on your system." )
196 const KLocalizedString detailsFail = ki18n(
"The QtSQL driver '%1' is required by your current Akonadi server configuration.\n"
197 "The following drivers are installed: %2.\n"
198 "Make sure the required driver is installed." )
200 .subs( availableDrivers.join( QLatin1String(
", " ) ) );
201 QStandardItem *item = 0;
202 if ( availableDrivers.contains( driver ) )
203 item = report( Success, ki18n(
"Database driver found." ), detailsOk );
205 item = report( Error, ki18n(
"Database driver not found." ), detailsFail );
206 item->setData( XdgBaseDirs::akonadiServerConfigFile( XdgBaseDirs::ReadWrite ), FileIncludeRole );
209 void SelfTestDialog::testMySQLServer()
211 if ( !useStandaloneMysqlServer() ) {
212 report( Skip, ki18n(
"MySQL server executable not tested." ),
213 ki18n(
"The current configuration does not require an internal MySQL server." ) );
217 const QString driver = serverSetting( QLatin1String(
"General" ),
"Driver", QLatin1String(
"QMYSQL" ) ).toString();
218 const QString serverPath = serverSetting( driver,
"ServerPath", QLatin1String(
"" ) ).toString();
220 const KLocalizedString details = ki18n(
"You have currently configured Akonadi to use the MySQL server '%1'.\n"
221 "Make sure you have the MySQL server installed, set the correct path and ensure you have the "
222 "necessary read and execution rights on the server executable. The server executable is typically "
223 "called 'mysqld'; its location varies depending on the distribution." ).subs( serverPath );
225 QFileInfo info( serverPath );
226 if ( !info.exists() )
227 report( Error, ki18n(
"MySQL server not found." ), details );
228 else if ( !info.isReadable() )
229 report( Error, ki18n(
"MySQL server not readable." ), details );
230 else if ( !info.isExecutable() )
231 report( Error, ki18n(
"MySQL server not executable." ), details );
232 else if ( !serverPath.contains( QLatin1String(
"mysqld" ) ) )
233 report( Warning, ki18n(
"MySQL found with unexpected name." ), details );
235 report( Success, ki18n(
"MySQL server found." ), details );
239 if ( runProcess( serverPath, QStringList() << QLatin1String(
"--version" ), result ) ) {
240 const KLocalizedString details = ki18n(
"MySQL server found: %1" ).subs( result );
241 report( Success, ki18n(
"MySQL server is executable." ), details );
243 const KLocalizedString details = ki18n(
"Executing the MySQL server '%1' failed with the following error message: '%2'" )
244 .subs( serverPath ).subs( result );
245 report( Error, ki18n(
"Executing the MySQL server failed." ), details );
249 void SelfTestDialog::testMySQLServerLog()
251 if ( !useStandaloneMysqlServer() ) {
252 report( Skip, ki18n(
"MySQL server error log not tested." ),
253 ki18n(
"The current configuration does not require an internal MySQL server." ) );
257 const QString logFileName = XdgBaseDirs::saveDir(
"data", QLatin1String(
"akonadi/db_data" ) )
258 + QDir::separator() + QString::fromLatin1(
"mysql.err" );
259 const QFileInfo logFileInfo( logFileName );
260 if ( !logFileInfo.exists() || logFileInfo.size() == 0 ) {
261 report( Success, ki18n(
"No current MySQL error log found." ),
262 ki18n(
"The MySQL server did not report any errors during this startup. The log can be found in '%1'." ).subs( logFileName ) );
265 QFile logFile( logFileName );
266 if ( !logFile.open( QFile::ReadOnly | QFile::Text ) ) {
267 report( Error, ki18n(
"MySQL error log not readable." ),
268 ki18n(
"A MySQL server error log file was found but is not readable: %1" ).subs( makeLink( logFileName ) ) );
271 bool warningsFound =
false;
272 QStandardItem *item = 0;
273 while ( !logFile.atEnd() ) {
274 const QString line = QString::fromUtf8( logFile.readLine() );
275 if ( line.contains( QLatin1String(
"error" ), Qt::CaseInsensitive ) ) {
276 item = report( Error, ki18n(
"MySQL server log contains errors." ),
277 ki18n(
"The MySQL server error log file '%1' contains errors." ).subs( makeLink( logFileName ) ) );
278 item->setData( logFileName, FileIncludeRole );
281 if ( !warningsFound && line.contains( QLatin1String(
"warn" ), Qt::CaseInsensitive ) ) {
282 warningsFound =
true;
285 if ( warningsFound ) {
286 item = report( Warning, ki18n(
"MySQL server log contains warnings." ),
287 ki18n(
"The MySQL server log file '%1' contains warnings." ).subs( makeLink( logFileName ) ) );
289 item = report( Success, ki18n(
"MySQL server log contains no errors." ),
290 ki18n(
"The MySQL server log file '%1' does not contain any errors or warnings." )
291 .subs( makeLink( logFileName ) ) );
293 item->setData( logFileName, FileIncludeRole );
298 void SelfTestDialog::testMySQLServerConfig()
300 if ( !useStandaloneMysqlServer() ) {
301 report( Skip, ki18n(
"MySQL server configuration not tested." ),
302 ki18n(
"The current configuration does not require an internal MySQL server." ) );
306 QStandardItem *item = 0;
307 const QString globalConfig = XdgBaseDirs::findResourceFile(
"config", QLatin1String(
"akonadi/mysql-global.conf" ) );
308 const QFileInfo globalConfigInfo( globalConfig );
309 if ( !globalConfig.isEmpty() && globalConfigInfo.exists() && globalConfigInfo.isReadable() ) {
310 item = report( Success, ki18n(
"MySQL server default configuration found." ),
311 ki18n(
"The default configuration for the MySQL server was found and is readable at %1." )
312 .subs( makeLink( globalConfig ) ) );
313 item->setData( globalConfig, FileIncludeRole );
315 report( Error, ki18n(
"MySQL server default configuration not found." ),
316 ki18n(
"The default configuration for the MySQL server was not found or was not readable. "
317 "Check your Akonadi installation is complete and you have all required access rights." ) );
320 const QString localConfig = XdgBaseDirs::findResourceFile(
"config", QLatin1String(
"akonadi/mysql-local.conf" ) );
321 const QFileInfo localConfigInfo( localConfig );
322 if ( localConfig.isEmpty() || !localConfigInfo.exists() ) {
323 report( Skip, ki18n(
"MySQL server custom configuration not available." ),
324 ki18n(
"The custom configuration for the MySQL server was not found but is optional." ) );
325 }
else if ( localConfigInfo.exists() && localConfigInfo.isReadable() ) {
326 item = report( Success, ki18n(
"MySQL server custom configuration found." ),
327 ki18n(
"The custom configuration for the MySQL server was found and is readable at %1" )
328 .subs( makeLink( localConfig ) ) );
329 item->setData( localConfig, FileIncludeRole );
331 report( Error, ki18n(
"MySQL server custom configuration not readable." ),
332 ki18n(
"The custom configuration for the MySQL server was found at %1 but is not readable. "
333 "Check your access rights." ).subs( makeLink( localConfig ) ) );
336 const QString actualConfig = XdgBaseDirs::saveDir(
"data", QLatin1String(
"akonadi" ) ) + QLatin1String(
"/mysql.conf" );
337 const QFileInfo actualConfigInfo( actualConfig );
338 if ( actualConfig.isEmpty() || !actualConfigInfo.exists() || !actualConfigInfo.isReadable() ) {
339 report( Error, ki18n(
"MySQL server configuration not found or not readable." ),
340 ki18n(
"The MySQL server configuration was not found or is not readable." ) );
342 item = report( Success, ki18n(
"MySQL server configuration is usable." ),
343 ki18n(
"The MySQL server configuration was found at %1 and is readable." ).subs( makeLink( actualConfig ) ) );
344 item->setData( actualConfig, FileIncludeRole );
348 void SelfTestDialog::testPSQLServer()
350 const QString dbname = serverSetting( QLatin1String(
"QPSQL" ),
"Name", QLatin1String(
"akonadi" )).toString();
351 const QString hostname = serverSetting( QLatin1String(
"QPSQL" ),
"Host", QLatin1String(
"localhost" )).toString();
352 const QString username = serverSetting( QLatin1String(
"QPSQL" ),
"User", QString() ).toString();
353 const QString password = serverSetting( QLatin1String(
"QPSQL" ),
"Password", QString() ).toString();
354 const int port = serverSetting( QLatin1String(
"QPSQL" ),
"Port", 5432).toInt();
356 QSqlDatabase db = QSqlDatabase::addDatabase( QLatin1String(
"QPSQL" ) );
357 db.setHostName( hostname );
358 db.setDatabaseName( dbname );
360 if ( !username.isEmpty() )
361 db.setUserName( username );
363 if ( !password.isEmpty() )
364 db.setPassword( password );
369 const KLocalizedString details = ki18n( db.lastError().text().toLatin1() );
370 report( Error, ki18n(
"Cannot connect to PostgreSQL server." ), details);
373 report( Success, ki18n(
"PostgreSQL server found." ),
374 ki18n(
"The PostgreSQL server was found and connection is working." ));
379 void SelfTestDialog::testAkonadiCtl()
381 const QString path = KStandardDirs::findExe( QLatin1String(
"akonadictl" ) );
382 if ( path.isEmpty() ) {
383 report( Error, ki18n(
"akonadictl not found" ),
384 ki18n(
"The program 'akonadictl' needs to be accessible in $PATH. "
385 "Make sure you have the Akonadi server installed." ) );
389 if ( runProcess( path, QStringList() << QLatin1String(
"--version" ), result ) ) {
390 report( Success, ki18n(
"akonadictl found and usable" ),
391 ki18n(
"The program '%1' to control the Akonadi server was found "
392 "and could be executed successfully.\nResult:\n%2" ).subs( path ).subs( result ) );
394 report( Error, ki18n(
"akonadictl found but not usable" ),
395 ki18n(
"The program '%1' to control the Akonadi server was found "
396 "but could not be executed successfully.\nResult:\n%2\n"
397 "Make sure the Akonadi server is installed correctly." ).subs( path ).subs( result ) );
401 void SelfTestDialog::testServerStatus()
403 if ( DBusConnectionPool::threadConnection().interface()->isServiceRegistered(
ServerManager::serviceName(ServerManager::Control) ) ) {
404 report( Success, ki18n(
"Akonadi control process registered at D-Bus." ),
405 ki18n(
"The Akonadi control process is registered at D-Bus which typically indicates it is operational." ) );
407 report( Error, ki18n(
"Akonadi control process not registered at D-Bus." ),
408 ki18n(
"The Akonadi control process is not registered at D-Bus which typically means it was not started "
409 "or encountered a fatal error during startup." ) );
412 if ( DBusConnectionPool::threadConnection().interface()->isServiceRegistered(
ServerManager::serviceName(ServerManager::Server) ) ) {
413 report( Success, ki18n(
"Akonadi server process registered at D-Bus." ),
414 ki18n(
"The Akonadi server process is registered at D-Bus which typically indicates it is operational." ) );
416 report( Error, ki18n(
"Akonadi server process not registered at D-Bus." ),
417 ki18n(
"The Akonadi server process is not registered at D-Bus which typically means it was not started "
418 "or encountered a fatal error during startup." ) );
422 void SelfTestDialog::testProtocolVersion()
424 if ( Internal::serverProtocolVersion() < 0 ) {
425 report( Skip, ki18n(
"Protocol version check not possible." ),
426 ki18n(
"Without a connection to the server it is not possible to check if the protocol version meets the requirements." ) );
429 if ( Internal::serverProtocolVersion() < SessionPrivate::minimumProtocolVersion() ) {
430 report( Error, ki18n(
"Server protocol version is too old." ),
431 ki18n(
"The server protocol version is %1, but at least version %2 is required. "
432 "Install a newer version of the Akonadi server." )
433 .subs( Internal::serverProtocolVersion() )
434 .subs( SessionPrivate::minimumProtocolVersion() ) );
436 report( Success, ki18n(
"Server protocol version is recent enough." ),
437 ki18n(
"The server Protocol version is %1, which equal or newer than the required version %2." )
438 .subs( Internal::serverProtocolVersion() )
439 .subs( SessionPrivate::minimumProtocolVersion() ) );
443 void SelfTestDialog::testResources()
446 bool resourceFound =
false;
447 foreach (
const AgentType &type, agentTypes ) {
448 if ( type.
capabilities().contains( QLatin1String(
"Resource" ) ) ) {
449 resourceFound =
true;
454 const QStringList pathList = XdgBaseDirs::findAllResourceDirs(
"data", QLatin1String(
"akonadi/agents" ) );
455 QStandardItem *item = 0;
456 if ( resourceFound ) {
457 item = report( Success, ki18n(
"Resource agents found." ), ki18n(
"At least one resource agent has been found." ) );
459 item = report( Error, ki18n(
"No resource agents found." ),
460 ki18n(
"No resource agents have been found, Akonadi is not usable without at least one. "
461 "This usually means that no resource agents are installed or that there is a setup problem. "
462 "The following paths have been searched: '%1'. "
463 "The XDG_DATA_DIRS environment variable is set to '%2'; make sure this includes all paths "
464 "where Akonadi agents are installed." )
465 .subs( pathList.join( QLatin1String(
" " ) ) )
466 .subs( QString::fromLocal8Bit( qgetenv(
"XDG_DATA_DIRS" ) ) ) );
468 item->setData( pathList, ListDirectoryRole );
469 item->setData( QByteArray(
"XDG_DATA_DIRS" ), EnvVarRole );
472 void Akonadi::SelfTestDialog::testServerLog()
474 QString serverLog = XdgBaseDirs::saveDir(
"data", QLatin1String(
"akonadi" ) )
475 + QDir::separator() + QString::fromLatin1(
"akonadiserver.error" );
476 QFileInfo info( serverLog );
477 if ( !info.exists() || info.size() <= 0 ) {
478 report( Success, ki18n(
"No current Akonadi server error log found." ),
479 ki18n(
"The Akonadi server did not report any errors during its current startup." ) );
481 QStandardItem *item = report( Error, ki18n(
"Current Akonadi server error log found." ),
482 ki18n(
"The Akonadi server reported errors during its current startup. The log can be found in %1." ).subs( makeLink( serverLog ) ) );
483 item->setData( serverLog, FileIncludeRole );
486 serverLog += QLatin1String(
".old" );
487 info.setFile( serverLog );
488 if ( !info.exists() || info.size() <= 0 ) {
489 report( Success, ki18n(
"No previous Akonadi server error log found." ),
490 ki18n(
"The Akonadi server did not report any errors during its previous startup." ) );
492 QStandardItem *item = report( Error, ki18n(
"Previous Akonadi server error log found." ),
493 ki18n(
"The Akonadi server reported errors during its previous startup. The log can be found in %1." ).subs( makeLink( serverLog ) ) );
494 item->setData( serverLog, FileIncludeRole );
498 void SelfTestDialog::testControlLog()
500 QString controlLog = XdgBaseDirs::saveDir(
"data", QLatin1String(
"akonadi" ) )
501 + QDir::separator() + QString::fromLatin1(
"akonadi_control.error" );
502 QFileInfo info( controlLog );
503 if ( !info.exists() || info.size() <= 0 ) {
504 report( Success, ki18n(
"No current Akonadi control error log found." ),
505 ki18n(
"The Akonadi control process did not report any errors during its current startup." ) );
507 QStandardItem *item = report( Error, ki18n(
"Current Akonadi control error log found." ),
508 ki18n(
"The Akonadi control process reported errors during its current startup. The log can be found in %1." ).subs( makeLink( controlLog ) ) );
509 item->setData( controlLog, FileIncludeRole );
512 controlLog += QLatin1String(
".old" );
513 info.setFile( controlLog );
514 if ( !info.exists() || info.size() <= 0 ) {
515 report( Success, ki18n(
"No previous Akonadi control error log found." ),
516 ki18n(
"The Akonadi control process did not report any errors during its previous startup." ) );
518 QStandardItem *item = report( Error, ki18n(
"Previous Akonadi control error log found." ),
519 ki18n(
"The Akonadi control process reported errors during its previous startup. The log can be found in %1." ).subs( makeLink( controlLog ) ) );
520 item->setData( controlLog, FileIncludeRole );
524 void SelfTestDialog::testRootUser()
527 if ( user.isSuperUser() ) {
528 report( Error, ki18n(
"Akonadi was started as root" ), ki18n(
"Running Internet-facing applications as root/administrator exposes you to many security risks. MySQL, used by this Akonadi installation, will not allow itself to run as root, to protect you from these risks." ) );
530 report( Success, ki18n(
"Akonadi is not running as root" ), ki18n(
"Akonadi is not running as a root/administrator user, which is the recommended setup for a secure system." ) );
534 QString SelfTestDialog::createReport()
537 QTextStream s( &result );
538 s <<
"Akonadi Server Self-Test Report" << endl;
539 s <<
"===============================" << endl;
541 for (
int i = 0; i < mTestModel->rowCount(); ++i ) {
542 QStandardItem *item = mTestModel->item( i );
544 s <<
"Test " << (i + 1) <<
": ";
545 switch ( item->data( ResultTypeRole ).toInt() ) {
549 s <<
"SUCCESS";
break;
551 s <<
"WARNING";
break;
556 s << endl <<
"--------" << endl;
558 s << item->data( SummaryRole ).toString() << endl;
559 s <<
"Details: " << item->data( DetailsRole ).toString() << endl;
560 if ( item->data( FileIncludeRole ).isValid() ) {
562 const QString fileName = item->data( FileIncludeRole ).toString();
564 if ( f.open( QFile::ReadOnly ) ) {
565 s <<
"File content of '" << fileName <<
"':" << endl;
566 s << f.readAll() << endl;
568 s <<
"File '" << fileName <<
"' could not be opened" << endl;
571 if ( item->data( ListDirectoryRole ).isValid() ) {
573 const QStringList pathList = item->data( ListDirectoryRole ).toStringList();
574 if ( pathList.isEmpty() )
575 s <<
"Directory list is empty." << endl;
576 foreach (
const QString &path, pathList ) {
577 s <<
"Directory listing of '" << path <<
"':" << endl;
579 dir.setFilter( QDir::AllEntries | QDir::NoDotAndDotDot );
580 foreach (
const QString &entry, dir.entryList() )
584 if ( item->data( EnvVarRole ).isValid() ) {
586 const QByteArray envVarName = item->data( EnvVarRole ).toByteArray();
587 const QByteArray envVarValue = qgetenv( envVarName );
588 s <<
"Environment variable " << envVarName <<
" is set to '" << envVarValue <<
"'" << endl;
597 void SelfTestDialog::saveReport()
599 const QString defaultFileName = QLatin1String(
"akonadi-selftest-report-" )
600 + QDate::currentDate().toString( QLatin1String(
"yyyyMMdd" ) )
601 + QLatin1String(
".txt" );
602 const QString fileName = KFileDialog::getSaveFileName( QUrl(defaultFileName), QString(),
this,
603 i18n(
"Save Test Report" ) );
604 if ( fileName.isEmpty() )
607 QFile file( fileName );
608 if ( !file.open( QFile::ReadWrite ) ) {
609 KMessageBox::error(
this, i18n(
"Could not open file '%1'", fileName ) );
613 file.write( createReport().toUtf8() );
617 void SelfTestDialog::copyReport()
619 #ifndef QT_NO_CLIPBOARD
620 QApplication::clipboard()->setText( createReport() );
624 void SelfTestDialog::linkActivated(
const QString & link)
626 KRun::runUrl( KUrl::fromPath( link ), QLatin1String(
"text/plain" ),
this );
631 #include "moc_selftestdialog_p.cpp"
static ServerManager * self()
Returns the singleton instance of this class, for connecting to its signals.
void hideIntroduction()
Hides the label with the introduction message.
static QString serviceName(ServiceType serviceType)
Returns the namespaced D-Bus service name for serviceType.
A representation of an agent type.
QList< AgentType > List
Describes a list of agent types.
QStringList capabilities() const
Returns the list of supported capabilities of the agent type.
SelfTestDialog(QWidget *parent=0)
Creates a new self test dialog.
AgentType::List types() const
Returns the list of all available agent types.
static AgentManager * self()
Returns the global instance of the agent manager.
State
Enum for the various states the server can be in.