00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "selftestdialog_p.h"
00021 #include "agentmanager.h"
00022 #include "session_p.h"
00023 #include "servermanager_p.h"
00024
00025 #include <akonadi/private/xdgbasedirs_p.h>
00026
00027 #include <KDebug>
00028 #include <KIcon>
00029 #include <KFileDialog>
00030 #include <KLocale>
00031 #include <KMessageBox>
00032 #include <KRun>
00033 #include <KStandardDirs>
00034 #include <KUser>
00035
00036 #include <QtCore/QFileInfo>
00037 #include <QtCore/QProcess>
00038 #include <QtCore/QSettings>
00039 #include <QtCore/QTextStream>
00040 #include <QtDBus/QtDBus>
00041 #include <QtGui/QApplication>
00042 #include <QtGui/QClipboard>
00043 #include <QtGui/QStandardItemModel>
00044 #include <QtSql/QSqlDatabase>
00045 #include <QtSql/QSqlError>
00046
00047
00048
00049 #define AKONADI_CONTROL_SERVICE QLatin1String( "org.freedesktop.Akonadi.Control" )
00050 #define AKONADI_SERVER_SERVICE QLatin1String( "org.freedesktop.Akonadi" )
00051 #define AKONADI_SEARCH_SERVICE QLatin1String( "org.kde.nepomuk.services.nepomukqueryservice" )
00052
00053 using namespace Akonadi;
00054
00055 static QString makeLink( const QString &file )
00056 {
00057 return QString::fromLatin1( "<a href=\"%1\">%2</a>" ).arg( file, file );
00058 }
00059
00060 enum SelfTestRole {
00061 ResultTypeRole = Qt::UserRole,
00062 FileIncludeRole,
00063 ListDirectoryRole,
00064 EnvVarRole,
00065 SummaryRole,
00066 DetailsRole
00067 };
00068
00069 SelfTestDialog::SelfTestDialog(QWidget * parent) :
00070 KDialog( parent )
00071 {
00072 setCaption( i18n( "Akonadi Server Self-Test" ) );
00073 setButtons( Close | User1 | User2 );
00074 setButtonText( User1, i18n( "Save Report..." ) );
00075 setButtonIcon( User1, KIcon( QString::fromLatin1( "document-save" ) ) );
00076 setButtonText( User2, i18n( "Copy Report to Clipboard" ) );
00077 setButtonIcon( User2, KIcon( QString::fromLatin1( "edit-copy" ) ) );
00078 showButtonSeparator( true );
00079 ui.setupUi( mainWidget() );
00080
00081 mTestModel = new QStandardItemModel( this );
00082 ui.testView->setModel( mTestModel );
00083 connect( ui.testView->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ),
00084 SLOT( selectionChanged( const QModelIndex& ) ) );
00085 connect( ui.detailsLabel, SIGNAL( linkActivated( const QString& ) ), SLOT( linkActivated( const QString& ) ) );
00086
00087 connect( this, SIGNAL( user1Clicked() ), SLOT( saveReport() ) );
00088 connect( this, SIGNAL( user2Clicked() ), SLOT( copyReport() ) );
00089
00090 connect( ServerManager::self(), SIGNAL( stateChanged( Akonadi::ServerManager::State ) ), SLOT( runTests() ) );
00091 runTests();
00092 }
00093
00094 void SelfTestDialog::hideIntroduction()
00095 {
00096 ui.introductionLabel->hide();
00097 }
00098
00099 QStandardItem* SelfTestDialog::report( ResultType type, const KLocalizedString & summary, const KLocalizedString & details)
00100 {
00101 QStandardItem *item = new QStandardItem( summary.toString() );
00102 switch ( type ) {
00103 case Skip:
00104 item->setIcon( KIcon( QString::fromLatin1( "dialog-ok" ) ) );
00105 break;
00106 case Success:
00107 item->setIcon( KIcon( QString::fromLatin1( "dialog-ok-apply" ) ) );
00108 break;
00109 case Warning:
00110 item->setIcon( KIcon( QString::fromLatin1( "dialog-warning" ) ) );
00111 break;
00112 case Error:
00113 default:
00114 item->setIcon( KIcon( QString::fromLatin1( "dialog-error" ) ) );
00115 }
00116 item->setEditable( false );
00117 item->setWhatsThis( details.toString() );
00118 item->setData( type, ResultTypeRole );
00119 item->setData( summary.toString( 0 ), SummaryRole );
00120 item->setData( details.toString( 0 ), DetailsRole );
00121 mTestModel->appendRow( item );
00122 return item;
00123 }
00124
00125 void SelfTestDialog::selectionChanged(const QModelIndex &index )
00126 {
00127 if ( index.isValid() ) {
00128 ui.detailsLabel->setText( index.data( Qt::WhatsThisRole ).toString() );
00129 ui.detailsGroup->setEnabled( true );
00130 } else {
00131 ui.detailsLabel->setText( QString() );
00132 ui.detailsGroup->setEnabled( false );
00133 }
00134 }
00135
00136 void SelfTestDialog::runTests()
00137 {
00138 mTestModel->clear();
00139
00140 const QString driver = serverSetting( QLatin1String( "General" ), "Driver", QLatin1String( "QMYSQL" ) ).toString();
00141 testSQLDriver();
00142 if (driver == QLatin1String( "QPSQL" )) {
00143 testPSQLServer();
00144 }
00145 else {
00146 testRootUser();
00147 testMySQLServer();
00148 testMySQLServerLog();
00149 testMySQLServerConfig();
00150 }
00151 testAkonadiCtl();
00152 testServerStatus();
00153 testSearchStatus();
00154 testProtocolVersion();
00155 testResources();
00156 testServerLog();
00157 testControlLog();
00158 }
00159
00160 QVariant SelfTestDialog::serverSetting(const QString & group, const char *key, const QVariant &def ) const
00161 {
00162 const QString serverConfigFile = XdgBaseDirs::akonadiServerConfigFile( XdgBaseDirs::ReadWrite );
00163 QSettings settings( serverConfigFile, QSettings::IniFormat );
00164 settings.beginGroup( group );
00165 return settings.value( QString::fromLatin1(key), def );
00166 }
00167
00168 bool SelfTestDialog::useStandaloneMysqlServer() const
00169 {
00170 const QString driver = serverSetting( QLatin1String( "General" ), "Driver", QLatin1String( "QMYSQL" ) ).toString();
00171 if ( driver != QLatin1String( "QMYSQL" ) )
00172 return false;
00173 const bool startServer = serverSetting( driver, "StartServer", true ).toBool();
00174 if ( !startServer )
00175 return false;
00176 return true;
00177 }
00178
00179 bool SelfTestDialog::runProcess(const QString & app, const QStringList & args, QString & result) const
00180 {
00181 QProcess proc;
00182 proc.start( app, args );
00183 const bool rv = proc.waitForFinished();
00184 result.clear();
00185 result += QString::fromLocal8Bit( proc.readAllStandardError() );
00186 result += QString::fromLocal8Bit( proc.readAllStandardOutput() );
00187 return rv;
00188 }
00189
00190 void SelfTestDialog::testSQLDriver()
00191 {
00192 const QString driver = serverSetting( QLatin1String( "General" ), "Driver", QLatin1String( "QMYSQL" ) ).toString();
00193 const QStringList availableDrivers = QSqlDatabase::drivers();
00194 const KLocalizedString detailsOk = ki18n( "The QtSQL driver '%1' is required by your current Akonadi server configuration and was found on your system." )
00195 .subs( driver );
00196 const KLocalizedString detailsFail = ki18n( "The QtSQL driver '%1' is required by your current Akonadi server configuration.\n"
00197 "The following drivers are installed: %2.\n"
00198 "Make sure the required driver is installed." )
00199 .subs( driver )
00200 .subs( availableDrivers.join( QLatin1String( ", " ) ) );
00201 QStandardItem *item = 0;
00202 if ( availableDrivers.contains( driver ) )
00203 item = report( Success, ki18n( "Database driver found." ), detailsOk );
00204 else
00205 item = report( Error, ki18n( "Database driver not found." ), detailsFail );
00206 item->setData( XdgBaseDirs::akonadiServerConfigFile( XdgBaseDirs::ReadWrite ), FileIncludeRole );
00207 }
00208
00209 void SelfTestDialog::testMySQLServer()
00210 {
00211 if ( !useStandaloneMysqlServer() ) {
00212 report( Skip, ki18n( "MySQL server executable not tested." ),
00213 ki18n( "The current configuration does not require an internal MySQL server." ) );
00214 return;
00215 }
00216
00217 const QString driver = serverSetting( QLatin1String( "General" ), "Driver", QLatin1String( "QMYSQL" ) ).toString();
00218 const QString serverPath = serverSetting( driver, "ServerPath", QLatin1String( "" ) ).toString();
00219
00220 const KLocalizedString details = ki18n( "You have currently configured Akonadi to use the MySQL server '%1'.\n"
00221 "Make sure you have the MySQL server installed, set the correct path and ensure you have the "
00222 "necessary read and execution rights on the server executable. The server executable is typically "
00223 "called 'mysqld'; its location varies depending on the distribution." ).subs( serverPath );
00224
00225 QFileInfo info( serverPath );
00226 if ( !info.exists() )
00227 report( Error, ki18n( "MySQL server not found." ), details );
00228 else if ( !info.isReadable() )
00229 report( Error, ki18n( "MySQL server not readable." ), details );
00230 else if ( !info.isExecutable() )
00231 report( Error, ki18n( "MySQL server not executable." ), details );
00232 else if ( !serverPath.contains( QLatin1String( "mysqld" ) ) )
00233 report( Warning, ki18n( "MySQL found with unexpected name." ), details );
00234 else
00235 report( Success, ki18n( "MySQL server found." ), details );
00236
00237
00238 QString result;
00239 if ( runProcess( serverPath, QStringList() << QLatin1String( "--version" ), result ) ) {
00240 const KLocalizedString details = ki18n( "MySQL server found: %1" ).subs( result );
00241 report( Success, ki18n( "MySQL server is executable." ), details );
00242 } else {
00243 const KLocalizedString details = ki18n( "Executing the MySQL server '%1' failed with the following error message: '%2'" )
00244 .subs( serverPath ).subs( result );
00245 report( Error, ki18n( "Executing the MySQL server failed." ), details );
00246 }
00247 }
00248
00249 void SelfTestDialog::testMySQLServerLog()
00250 {
00251 if ( !useStandaloneMysqlServer() ) {
00252 report( Skip, ki18n( "MySQL server error log not tested." ),
00253 ki18n( "The current configuration does not require an internal MySQL server." ) );
00254 return;
00255 }
00256
00257 const QString logFileName = XdgBaseDirs::saveDir( "data", QLatin1String( "akonadi/db_data" ) )
00258 + QDir::separator() + QString::fromLatin1( "mysql.err" );
00259 const QFileInfo logFileInfo( logFileName );
00260 if ( !logFileInfo.exists() || logFileInfo.size() == 0 ) {
00261 report( Success, ki18n( "No current MySQL error log found." ),
00262 ki18n( "The MySQL server did not report any errors during this startup. The log can be found in '%1'." ).subs( logFileName ) );
00263 return;
00264 }
00265 QFile logFile( logFileName );
00266 if ( !logFile.open( QFile::ReadOnly | QFile::Text ) ) {
00267 report( Error, ki18n( "MySQL error log not readable." ),
00268 ki18n( "A MySQL server error log file was found but is not readable: %1" ).subs( makeLink( logFileName ) ) );
00269 return;
00270 }
00271 bool warningsFound = false;
00272 QStandardItem *item = 0;
00273 while ( !logFile.atEnd() ) {
00274 const QString line = QString::fromUtf8( logFile.readLine() );
00275 if ( line.contains( QLatin1String( "error" ), Qt::CaseInsensitive ) ) {
00276 item = report( Error, ki18n( "MySQL server log contains errors." ),
00277 ki18n( "The MySQL server error log file '%1' contains errors." ).subs( makeLink( logFileName ) ) );
00278 item->setData( logFileName, FileIncludeRole );
00279 return;
00280 }
00281 if ( !warningsFound && line.contains( QLatin1String( "warn" ), Qt::CaseInsensitive ) ) {
00282 warningsFound = true;
00283 }
00284 }
00285 if ( warningsFound ) {
00286 item = report( Warning, ki18n( "MySQL server log contains warnings." ),
00287 ki18n( "The MySQL server log file '%1' contains warnings." ).subs( makeLink( logFileName ) ) );
00288 } else {
00289 item = report( Success, ki18n( "MySQL server log contains no errors." ),
00290 ki18n( "The MySQL server log file '%1' does not contain any errors or warnings." )
00291 .subs( makeLink( logFileName ) ) );
00292 }
00293 item->setData( logFileName, FileIncludeRole );
00294
00295 logFile.close();
00296 }
00297
00298 void SelfTestDialog::testMySQLServerConfig()
00299 {
00300 if ( !useStandaloneMysqlServer() ) {
00301 report( Skip, ki18n( "MySQL server configuration not tested." ),
00302 ki18n( "The current configuration does not require an internal MySQL server." ) );
00303 return;
00304 }
00305
00306 QStandardItem *item = 0;
00307 const QString globalConfig = XdgBaseDirs::findResourceFile( "config", QLatin1String( "akonadi/mysql-global.conf" ) );
00308 const QFileInfo globalConfigInfo( globalConfig );
00309 if ( !globalConfig.isEmpty() && globalConfigInfo.exists() && globalConfigInfo.isReadable() ) {
00310 item = report( Success, ki18n( "MySQL server default configuration found." ),
00311 ki18n( "The default configuration for the MySQL server was found and is readable at %1." )
00312 .subs( makeLink( globalConfig ) ) );
00313 item->setData( globalConfig, FileIncludeRole );
00314 } else {
00315 report( Error, ki18n( "MySQL server default configuration not found." ),
00316 ki18n( "The default configuration for the MySQL server was not found or was not readable. "
00317 "Check your Akonadi installation is complete and you have all required access rights." ) );
00318 }
00319
00320 const QString localConfig = XdgBaseDirs::findResourceFile( "config", QLatin1String( "akonadi/mysql-local.conf" ) );
00321 const QFileInfo localConfigInfo( localConfig );
00322 if ( localConfig.isEmpty() || !localConfigInfo.exists() ) {
00323 report( Skip, ki18n( "MySQL server custom configuration not available." ),
00324 ki18n( "The custom configuration for the MySQL server was not found but is optional." ) );
00325 } else if ( localConfigInfo.exists() && localConfigInfo.isReadable() ) {
00326 item = report( Success, ki18n( "MySQL server custom configuration found." ),
00327 ki18n( "The custom configuration for the MySQL server was found and is readable at %1" )
00328 .subs( makeLink( localConfig ) ) );
00329 item->setData( localConfig, FileIncludeRole );
00330 } else {
00331 report( Error, ki18n( "MySQL server custom configuration not readable." ),
00332 ki18n( "The custom configuration for the MySQL server was found at %1 but is not readable. "
00333 "Check your access rights." ).subs( makeLink( localConfig ) ) );
00334 }
00335
00336 const QString actualConfig = XdgBaseDirs::saveDir( "data", QLatin1String( "akonadi" ) ) + QLatin1String( "/mysql.conf" );
00337 const QFileInfo actualConfigInfo( actualConfig );
00338 if ( actualConfig.isEmpty() || !actualConfigInfo.exists() || !actualConfigInfo.isReadable() ) {
00339 report( Error, ki18n( "MySQL server configuration not found or not readable." ),
00340 ki18n( "The MySQL server configuration was not found or is not readable." ) );
00341 } else {
00342 item = report( Success, ki18n( "MySQL server configuration is usable." ),
00343 ki18n( "The MySQL server configuration was found at %1 and is readable." ).subs( makeLink( actualConfig ) ) );
00344 item->setData( actualConfig, FileIncludeRole );
00345 }
00346 }
00347
00348 void SelfTestDialog::testPSQLServer()
00349 {
00350 const QString dbname = serverSetting( QLatin1String( "QPSQL" ), "Name", QLatin1String( "akonadi" )).toString();
00351 const QString hostname = serverSetting( QLatin1String( "QPSQL" ), "Host", QLatin1String( "localhost" )).toString();
00352 const QString username = serverSetting( QLatin1String( "QPSQL" ), "User", QString() ).toString();
00353 const QString password = serverSetting( QLatin1String( "QPSQL" ), "Password", QString() ).toString();
00354 const int port = serverSetting( QLatin1String( "QPSQL" ), "Port", 5432).toInt();
00355
00356 QSqlDatabase db = QSqlDatabase::addDatabase( QLatin1String( "QPSQL" ) );
00357 db.setHostName( hostname );
00358 db.setDatabaseName( dbname );
00359
00360 if ( !username.isEmpty() )
00361 db.setUserName( username );
00362
00363 if ( !password.isEmpty() )
00364 db.setPassword( password );
00365
00366 db.setPort( port );
00367
00368 if ( !db.open() ) {
00369 const KLocalizedString details = ki18n( db.lastError().text().toLatin1() );
00370 report( Error, ki18n( "Cannot connect to PostgreSQL server." ), details);
00371 }
00372 else {
00373 report( Success, ki18n( "PostgreSQL server found." ),
00374 ki18n( "The PostgreSQL server was found and connection is working." ));
00375 }
00376 db.close();
00377 }
00378
00379 void SelfTestDialog::testAkonadiCtl()
00380 {
00381 const QString path = KStandardDirs::findExe( QLatin1String( "akonadictl" ) );
00382 if ( path.isEmpty() ) {
00383 report( Error, ki18n( "akonadictl not found" ),
00384 ki18n( "The program 'akonadictl' needs to be accessible in $PATH. "
00385 "Make sure you have the Akonadi server installed." ) );
00386 return;
00387 }
00388 QString result;
00389 if ( runProcess( path, QStringList() << QLatin1String( "--version" ), result ) ) {
00390 report( Success, ki18n( "akonadictl found and usable" ),
00391 ki18n( "The program '%1' to control the Akonadi server was found "
00392 "and could be executed successfully.\nResult:\n%2" ).subs( path ).subs( result ) );
00393 } else {
00394 report( Error, ki18n( "akonadictl found but not usable" ),
00395 ki18n( "The program '%1' to control the Akonadi server was found "
00396 "but could not be executed successfully.\nResult:\n%2\n"
00397 "Make sure the Akonadi server is installed correctly." ).subs( path ).subs( result ) );
00398 }
00399 }
00400
00401 void SelfTestDialog::testServerStatus()
00402 {
00403 if ( QDBusConnection::sessionBus().interface()->isServiceRegistered( AKONADI_CONTROL_SERVICE ) ) {
00404 report( Success, ki18n( "Akonadi control process registered at D-Bus." ),
00405 ki18n( "The Akonadi control process is registered at D-Bus which typically indicates it is operational." ) );
00406 } else {
00407 report( Error, ki18n( "Akonadi control process not registered at D-Bus." ),
00408 ki18n( "The Akonadi control process is not registered at D-Bus which typically means it was not started "
00409 "or encountered a fatal error during startup." ) );
00410 }
00411
00412 if ( QDBusConnection::sessionBus().interface()->isServiceRegistered( AKONADI_SERVER_SERVICE ) ) {
00413 report( Success, ki18n( "Akonadi server process registered at D-Bus." ),
00414 ki18n( "The Akonadi server process is registered at D-Bus which typically indicates it is operational." ) );
00415 } else {
00416 report( Error, ki18n( "Akonadi server process not registered at D-Bus." ),
00417 ki18n( "The Akonadi server process is not registered at D-Bus which typically means it was not started "
00418 "or encountered a fatal error during startup." ) );
00419 }
00420 }
00421
00422 void SelfTestDialog::testSearchStatus()
00423 {
00424 bool searchAvailable = false;
00425 if ( QDBusConnection::sessionBus().interface()->isServiceRegistered( AKONADI_SEARCH_SERVICE ) ) {
00426 searchAvailable = true;
00427 report( Success, ki18n( "Nepomuk search service registered at D-Bus." ),
00428 ki18n( "The Nepomuk search service is registered at D-Bus which typically indicates it is operational." ) );
00429 } else {
00430 report( Error, ki18n( "Nepomuk search service not registered at D-Bus." ),
00431 ki18n( "The Nepomuk search service is not registered at D-Bus which typically means it was not started "
00432 "or encountered a fatal error during startup." ) );
00433 }
00434
00435 if ( searchAvailable ) {
00436
00437 QDBusInterface interface( QLatin1String( "org.kde.NepomukStorage" ), QLatin1String( "/nepomukstorage" ) );
00438 const QDBusReply<QString> reply = interface.call( QLatin1String( "usedSopranoBackend" ) );
00439 if ( reply.isValid() ) {
00440 const QString name = reply.value();
00441
00442
00443 if ( name.contains( QLatin1String( "redland" ) ) ) {
00444 report( Error, ki18n( "Nepomuk search service uses inappropriate backend." ),
00445 ki18n( "The Nepomuk search service uses the '%1' backend, which is not "
00446 "recommended for use with Akonadi." ).subs( name ) );
00447 } else {
00448 report( Success, ki18n( "Nepomuk search service uses an appropriate backend. " ),
00449 ki18n( "The Nepomuk search service uses one of the recommended backends." ) );
00450 }
00451 }
00452 }
00453 }
00454
00455 void SelfTestDialog::testProtocolVersion()
00456 {
00457 if ( Internal::serverProtocolVersion() < 0 ) {
00458 report( Skip, ki18n( "Protocol version check not possible." ),
00459 ki18n( "Without a connection to the server it is not possible to check if the protocol version meets the requirements." ) );
00460 return;
00461 }
00462 if ( Internal::serverProtocolVersion() < SessionPrivate::minimumProtocolVersion() ) {
00463 report( Error, ki18n( "Server protocol version is too old." ),
00464 ki18n( "The server protocol version is %1, but at least version %2 is required. "
00465 "Install a newer version of the Akonadi server." )
00466 .subs( Internal::serverProtocolVersion() )
00467 .subs( SessionPrivate::minimumProtocolVersion() ) );
00468 } else {
00469 report( Success, ki18n( "Server protocol version is recent enough." ),
00470 ki18n( "The server Protocol version is %1, which equal or newer than the required version %2." )
00471 .subs( Internal::serverProtocolVersion() )
00472 .subs( SessionPrivate::minimumProtocolVersion() ) );
00473 }
00474 }
00475
00476 void SelfTestDialog::testResources()
00477 {
00478 AgentType::List agentTypes = AgentManager::self()->types();
00479 bool resourceFound = false;
00480 foreach ( const AgentType &type, agentTypes ) {
00481 if ( type.capabilities().contains( QLatin1String( "Resource" ) ) ) {
00482 resourceFound = true;
00483 break;
00484 }
00485 }
00486
00487 const QStringList pathList = XdgBaseDirs::findAllResourceDirs( "data", QLatin1String( "akonadi/agents" ) );
00488 QStandardItem *item = 0;
00489 if ( resourceFound ) {
00490 item = report( Success, ki18n( "Resource agents found." ), ki18n( "At least one resource agent has been found." ) );
00491 } else {
00492 item = report( Error, ki18n( "No resource agents found." ),
00493 ki18n( "No resource agents have been found, Akonadi is not usable without at least one. "
00494 "This usually means that no resource agents are installed or that there is a setup problem. "
00495 "The following paths have been searched: '%1'. "
00496 "The XDG_DATA_DIRS environment variable is set to '%2'; make sure this includes all paths "
00497 "where Akonadi agents are installed." )
00498 .subs( pathList.join( QLatin1String( " " ) ) )
00499 .subs( QString::fromLocal8Bit( qgetenv( "XDG_DATA_DIRS" ) ) ) );
00500 }
00501 item->setData( pathList, ListDirectoryRole );
00502 item->setData( QByteArray( "XDG_DATA_DIRS" ), EnvVarRole );
00503 }
00504
00505 void Akonadi::SelfTestDialog::testServerLog()
00506 {
00507 QString serverLog = XdgBaseDirs::saveDir( "data", QLatin1String( "akonadi" ) )
00508 + QDir::separator() + QString::fromLatin1( "akonadiserver.error" );
00509 QFileInfo info( serverLog );
00510 if ( !info.exists() || info.size() <= 0 ) {
00511 report( Success, ki18n( "No current Akonadi server error log found." ),
00512 ki18n( "The Akonadi server did not report any errors during its current startup." ) );
00513 } else {
00514 QStandardItem *item = report( Error, ki18n( "Current Akonadi server error log found." ),
00515 ki18n( "The Akonadi server reported errors during its current startup. The log can be found in %1." ).subs( makeLink( serverLog ) ) );
00516 item->setData( serverLog, FileIncludeRole );
00517 }
00518
00519 serverLog += QLatin1String( ".old" );
00520 info.setFile( serverLog );
00521 if ( !info.exists() || info.size() <= 0 ) {
00522 report( Success, ki18n( "No previous Akonadi server error log found." ),
00523 ki18n( "The Akonadi server did not report any errors during its previous startup." ) );
00524 } else {
00525 QStandardItem *item = report( Error, ki18n( "Previous Akonadi server error log found." ),
00526 ki18n( "The Akonadi server reported errors during its previous startup. The log can be found in %1." ).subs( makeLink( serverLog ) ) );
00527 item->setData( serverLog, FileIncludeRole );
00528 }
00529 }
00530
00531 void SelfTestDialog::testControlLog()
00532 {
00533 QString controlLog = XdgBaseDirs::saveDir( "data", QLatin1String( "akonadi" ) )
00534 + QDir::separator() + QString::fromLatin1( "akonadi_control.error" );
00535 QFileInfo info( controlLog );
00536 if ( !info.exists() || info.size() <= 0 ) {
00537 report( Success, ki18n( "No current Akonadi control error log found." ),
00538 ki18n( "The Akonadi control process did not report any errors during its current startup." ) );
00539 } else {
00540 QStandardItem *item = report( Error, ki18n( "Current Akonadi control error log found." ),
00541 ki18n( "The Akonadi control process reported errors during its current startup. The log can be found in %1." ).subs( makeLink( controlLog ) ) );
00542 item->setData( controlLog, FileIncludeRole );
00543 }
00544
00545 controlLog += QLatin1String( ".old" );
00546 info.setFile( controlLog );
00547 if ( !info.exists() || info.size() <= 0 ) {
00548 report( Success, ki18n( "No previous Akonadi control error log found." ),
00549 ki18n( "The Akonadi control process did not report any errors during its previous startup." ) );
00550 } else {
00551 QStandardItem *item = report( Error, ki18n( "Previous Akonadi control error log found." ),
00552 ki18n( "The Akonadi control process reported errors during its previous startup. The log can be found in %1." ).subs( makeLink( controlLog ) ) );
00553 item->setData( controlLog, FileIncludeRole );
00554 }
00555 }
00556
00557
00558 void SelfTestDialog::testRootUser()
00559 {
00560 KUser user;
00561 if ( user.isSuperUser() ) {
00562 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." ) );
00563 } else {
00564 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." ) );
00565 }
00566 }
00567
00568 QString SelfTestDialog::createReport()
00569 {
00570 QString result;
00571 QTextStream s( &result );
00572 s << "Akonadi Server Self-Test Report" << endl;
00573 s << "===============================" << endl;
00574
00575 for ( int i = 0; i < mTestModel->rowCount(); ++i ) {
00576 QStandardItem *item = mTestModel->item( i );
00577 s << endl;
00578 s << "Test " << (i + 1) << ": ";
00579 switch ( item->data( ResultTypeRole ).toInt() ) {
00580 case Skip:
00581 s << "SKIP"; break;
00582 case Success:
00583 s << "SUCCESS"; break;
00584 case Warning:
00585 s << "WARNING"; break;
00586 case Error:
00587 default:
00588 s << "ERROR"; break;
00589 }
00590 s << endl << "--------" << endl;
00591 s << endl;
00592 s << item->data( SummaryRole ).toString() << endl;
00593 s << "Details: " << item->data( DetailsRole ).toString() << endl;
00594 if ( item->data( FileIncludeRole ).isValid() ) {
00595 s << endl;
00596 const QString fileName = item->data( FileIncludeRole ).toString();
00597 QFile f( fileName );
00598 if ( f.open( QFile::ReadOnly ) ) {
00599 s << "File content of '" << fileName << "':" << endl;
00600 s << f.readAll() << endl;
00601 } else {
00602 s << "File '" << fileName << "' could not be opened" << endl;
00603 }
00604 }
00605 if ( item->data( ListDirectoryRole ).isValid() ) {
00606 s << endl;
00607 const QStringList pathList = item->data( ListDirectoryRole ).toStringList();
00608 if ( pathList.isEmpty() )
00609 s << "Directory list is empty." << endl;
00610 foreach ( const QString &path, pathList ) {
00611 s << "Directory listing of '" << path << "':" << endl;
00612 QDir dir( path );
00613 dir.setFilter( QDir::AllEntries | QDir::NoDotAndDotDot );
00614 foreach ( const QString &entry, dir.entryList() )
00615 s << entry << endl;
00616 }
00617 }
00618 if ( item->data( EnvVarRole ).isValid() ) {
00619 s << endl;
00620 const QByteArray envVarName = item->data( EnvVarRole ).toByteArray();
00621 const QByteArray envVarValue = qgetenv( envVarName );
00622 s << "Environment variable " << envVarName << " is set to '" << envVarValue << "'" << endl;
00623 }
00624 }
00625
00626 s << endl;
00627 s.flush();
00628 return result;
00629 }
00630
00631 void SelfTestDialog::saveReport()
00632 {
00633 const QString defaultFileName = QLatin1String( "akonadi-selftest-report-" )
00634 + QDate::currentDate().toString( QLatin1String( "yyyyMMdd" ) )
00635 + QLatin1String( ".txt" );
00636 const QString fileName = KFileDialog::getSaveFileName( defaultFileName, QString(), this,
00637 i18n( "Save Test Report" ) );
00638 if ( fileName.isEmpty() )
00639 return;
00640
00641 QFile file( fileName );
00642 if ( !file.open( QFile::ReadWrite ) ) {
00643 KMessageBox::error( this, i18n( "Could not open file '%1'", fileName ) );
00644 return;
00645 }
00646
00647 file.write( createReport().toUtf8() );
00648 file.close();
00649 }
00650
00651 void SelfTestDialog::copyReport()
00652 {
00653 QApplication::clipboard()->setText( createReport() );
00654 }
00655
00656 void SelfTestDialog::linkActivated(const QString & link)
00657 {
00658 KRun::runUrl( KUrl::fromPath( link ), QLatin1String( "text/plain" ), this );
00659 }
00660
00661
00662
00663 #include "selftestdialog_p.moc"