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;
60 ResultTypeRole = Qt::UserRole,
71 setCaption(i18n(
"Akonadi Server Self-Test"));
72 setButtons(Close | User1 | User2);
73 setButtonText(User1, i18n(
"Save Report..."));
75 setButtonText(User2, i18n(
"Copy Report to Clipboard"));
77 showButtonSeparator(
true);
78 ui.setupUi(mainWidget());
81 ui.testView->setModel(mTestModel);
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)
117 item->
setData(type, ResultTypeRole);
118 item->
setData(summary.toString(0), SummaryRole);
119 item->
setData(details.toString(0), DetailsRole);
124 void SelfTestDialog::selectionChanged(
const QModelIndex &index)
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()
148 testMySQLServerLog();
149 testMySQLServerConfig();
153 testProtocolVersion();
161 const QString serverConfigFile = XdgBaseDirs::akonadiServerConfigFile(XdgBaseDirs::ReadWrite);
162 QSettings settings(serverConfigFile, QSettings::IniFormat);
163 settings.beginGroup(group);
167 bool SelfTestDialog::useStandaloneMysqlServer()
const
173 const bool startServer = serverSetting(driver,
"StartServer",
true).
toBool();
183 proc.
start(app, args);
191 void SelfTestDialog::testSQLDriver()
195 const KLocalizedString detailsOk = ki18n(
"The QtSQL driver '%1' is required by your current Akonadi server configuration and was found on your system.")
197 const KLocalizedString detailsFail = ki18n(
"The QtSQL driver '%1' is required by your current Akonadi server configuration.\n"
198 "The following drivers are installed: %2.\n"
199 "Make sure the required driver is installed.")
203 if (availableDrivers.
contains(driver)) {
204 item = report(Success, ki18n(
"Database driver found."), detailsOk);
206 item = report(Error, ki18n(
"Database driver not found."), detailsFail);
208 item->
setData(XdgBaseDirs::akonadiServerConfigFile(XdgBaseDirs::ReadWrite), FileIncludeRole);
211 void SelfTestDialog::testMySQLServer()
213 if (!useStandaloneMysqlServer()) {
214 report(Skip, ki18n(
"MySQL server executable not tested."),
215 ki18n(
"The current configuration does not require an internal MySQL server."));
222 const KLocalizedString details = ki18n(
"You have currently configured Akonadi to use the MySQL server '%1'.\n"
223 "Make sure you have the MySQL server installed, set the correct path and ensure you have the "
224 "necessary read and execution rights on the server executable. The server executable is typically "
225 "called 'mysqld'; its location varies depending on the distribution.").subs(serverPath);
228 if (!info.exists()) {
229 report(Error, ki18n(
"MySQL server not found."), details);
230 }
else if (!info.isReadable()) {
231 report(Error, ki18n(
"MySQL server not readable."), details);
232 }
else if (!info.isExecutable()) {
233 report(Error, ki18n(
"MySQL server not executable."), details);
235 report(Warning, ki18n(
"MySQL found with unexpected name."), details);
237 report(Success, ki18n(
"MySQL server found."), details);
243 const KLocalizedString details = ki18n(
"MySQL server found: %1").subs(result);
244 report(Success, ki18n(
"MySQL server is executable."), details);
246 const KLocalizedString details = ki18n(
"Executing the MySQL server '%1' failed with the following error message: '%2'")
247 .subs(serverPath).subs(result);
248 report(Error, ki18n(
"Executing the MySQL server failed."), details);
252 void SelfTestDialog::testMySQLServerLog()
254 if (!useStandaloneMysqlServer()) {
255 report(Skip, ki18n(
"MySQL server error log not tested."),
256 ki18n(
"The current configuration does not require an internal MySQL server."));
262 const QFileInfo logFileInfo(logFileName);
263 if (!logFileInfo.exists() || logFileInfo.size() == 0) {
264 report(Success, ki18n(
"No current MySQL error log found."),
265 ki18n(
"The MySQL server did not report any errors during this startup. The log can be found in '%1'.").subs(logFileName));
268 QFile logFile(logFileName);
269 if (!logFile.open(QFile::ReadOnly | QFile::Text)) {
270 report(Error, ki18n(
"MySQL error log not readable."),
271 ki18n(
"A MySQL server error log file was found but is not readable: %1").subs(makeLink(logFileName)));
274 bool warningsFound =
false;
276 while (!logFile.atEnd()) {
279 item = report(Error, ki18n(
"MySQL server log contains errors."),
280 ki18n(
"The MySQL server error log file '%1' contains errors.").subs(makeLink(logFileName)));
281 item->
setData(logFileName, FileIncludeRole);
285 warningsFound =
true;
289 item = report(Warning, ki18n(
"MySQL server log contains warnings."),
290 ki18n(
"The MySQL server log file '%1' contains warnings.").subs(makeLink(logFileName)));
292 item = report(Success, ki18n(
"MySQL server log contains no errors."),
293 ki18n(
"The MySQL server log file '%1' does not contain any errors or warnings.")
294 .subs(makeLink(logFileName)));
296 item->
setData(logFileName, FileIncludeRole);
301 void SelfTestDialog::testMySQLServerConfig()
303 if (!useStandaloneMysqlServer()) {
304 report(Skip, ki18n(
"MySQL server configuration not tested."),
305 ki18n(
"The current configuration does not require an internal MySQL server."));
310 const QString globalConfig = XdgBaseDirs::findResourceFile(
"config",
QLatin1String(
"akonadi/mysql-global.conf"));
311 const QFileInfo globalConfigInfo(globalConfig);
312 if (!globalConfig.
isEmpty() && globalConfigInfo.exists() && globalConfigInfo.isReadable()) {
313 item = report(Success, ki18n(
"MySQL server default configuration found."),
314 ki18n(
"The default configuration for the MySQL server was found and is readable at %1.")
315 .subs(makeLink(globalConfig)));
316 item->
setData(globalConfig, FileIncludeRole);
318 report(Error, ki18n(
"MySQL server default configuration not found."),
319 ki18n(
"The default configuration for the MySQL server was not found or was not readable. "
320 "Check your Akonadi installation is complete and you have all required access rights."));
323 const QString localConfig = XdgBaseDirs::findResourceFile(
"config",
QLatin1String(
"akonadi/mysql-local.conf"));
324 const QFileInfo localConfigInfo(localConfig);
325 if (localConfig.
isEmpty() || !localConfigInfo.exists()) {
326 report(Skip, ki18n(
"MySQL server custom configuration not available."),
327 ki18n(
"The custom configuration for the MySQL server was not found but is optional."));
328 }
else if (localConfigInfo.exists() && localConfigInfo.isReadable()) {
329 item = report(Success, ki18n(
"MySQL server custom configuration found."),
330 ki18n(
"The custom configuration for the MySQL server was found and is readable at %1")
331 .subs(makeLink(localConfig)));
332 item->
setData(localConfig, FileIncludeRole);
334 report(Error, ki18n(
"MySQL server custom configuration not readable."),
335 ki18n(
"The custom configuration for the MySQL server was found at %1 but is not readable. "
336 "Check your access rights.").subs(makeLink(localConfig)));
340 const QFileInfo actualConfigInfo(actualConfig);
341 if (actualConfig.
isEmpty() || !actualConfigInfo.exists() || !actualConfigInfo.isReadable()) {
342 report(Error, ki18n(
"MySQL server configuration not found or not readable."),
343 ki18n(
"The MySQL server configuration was not found or is not readable."));
345 item = report(Success, ki18n(
"MySQL server configuration is usable."),
346 ki18n(
"The MySQL server configuration was found at %1 and is readable.").subs(makeLink(actualConfig)));
347 item->
setData(actualConfig, FileIncludeRole);
351 void SelfTestDialog::testPSQLServer()
375 report(Error, ki18n(
"Cannot connect to PostgreSQL server."), details);
377 report(Success, ki18n(
"PostgreSQL server found."),
378 ki18n(
"The PostgreSQL server was found and connection is working."));
383 void SelfTestDialog::testAkonadiCtl()
387 report(Error, ki18n(
"akonadictl not found"),
388 ki18n(
"The program 'akonadictl' needs to be accessible in $PATH. "
389 "Make sure you have the Akonadi server installed."));
394 report(Success, ki18n(
"akonadictl found and usable"),
395 ki18n(
"The program '%1' to control the Akonadi server was found "
396 "and could be executed successfully.\nResult:\n%2").subs(path).subs(result));
398 report(Error, ki18n(
"akonadictl found but not usable"),
399 ki18n(
"The program '%1' to control the Akonadi server was found "
400 "but could not be executed successfully.\nResult:\n%2\n"
401 "Make sure the Akonadi server is installed correctly.").subs(path).subs(result));
405 void SelfTestDialog::testServerStatus()
407 if (DBusConnectionPool::threadConnection().interface()->isServiceRegistered(
ServerManager::serviceName(ServerManager::Control))) {
408 report(Success, ki18n(
"Akonadi control process registered at D-Bus."),
409 ki18n(
"The Akonadi control process is registered at D-Bus which typically indicates it is operational."));
411 report(Error, ki18n(
"Akonadi control process not registered at D-Bus."),
412 ki18n(
"The Akonadi control process is not registered at D-Bus which typically means it was not started "
413 "or encountered a fatal error during startup."));
416 if (DBusConnectionPool::threadConnection().interface()->isServiceRegistered(
ServerManager::serviceName(ServerManager::Server))) {
417 report(Success, ki18n(
"Akonadi server process registered at D-Bus."),
418 ki18n(
"The Akonadi server process is registered at D-Bus which typically indicates it is operational."));
420 report(Error, ki18n(
"Akonadi server process not registered at D-Bus."),
421 ki18n(
"The Akonadi server process is not registered at D-Bus which typically means it was not started "
422 "or encountered a fatal error during startup."));
426 void SelfTestDialog::testProtocolVersion()
428 if (Internal::serverProtocolVersion() < 0) {
429 report(Skip, ki18n(
"Protocol version check not possible."),
430 ki18n(
"Without a connection to the server it is not possible to check if the protocol version meets the requirements."));
433 if (Internal::serverProtocolVersion() < SessionPrivate::minimumProtocolVersion()) {
434 report(Error, ki18n(
"Server protocol version is too old."),
435 ki18n(
"The server protocol version is %1, but at least version %2 is required. "
436 "Install a newer version of the Akonadi server.")
437 .subs(Internal::serverProtocolVersion())
438 .subs(SessionPrivate::minimumProtocolVersion()));
440 report(Success, ki18n(
"Server protocol version is recent enough."),
441 ki18n(
"The server Protocol version is %1, which equal or newer than the required version %2.")
442 .subs(Internal::serverProtocolVersion())
443 .subs(SessionPrivate::minimumProtocolVersion()));
447 void SelfTestDialog::testResources()
450 bool resourceFound =
false;
451 foreach (
const AgentType &type, agentTypes) {
453 resourceFound =
true;
461 item = report(Success, ki18n(
"Resource agents found."), ki18n(
"At least one resource agent has been found."));
463 item = report(Error, ki18n(
"No resource agents found."),
464 ki18n(
"No resource agents have been found, Akonadi is not usable without at least one. "
465 "This usually means that no resource agents are installed or that there is a setup problem. "
466 "The following paths have been searched: '%1'. "
467 "The XDG_DATA_DIRS environment variable is set to '%2'; make sure this includes all paths "
468 "where Akonadi agents are installed.")
472 item->
setData(pathList, ListDirectoryRole);
476 void Akonadi::SelfTestDialog::testServerLog()
481 if (!info.exists() || info.size() <= 0) {
482 report(Success, ki18n(
"No current Akonadi server error log found."),
483 ki18n(
"The Akonadi server did not report any errors during its current startup."));
485 QStandardItem *item = report(Error, ki18n(
"Current Akonadi server error log found."),
486 ki18n(
"The Akonadi server reported errors during its current startup. The log can be found in %1.").subs(makeLink(serverLog)));
487 item->
setData(serverLog, FileIncludeRole);
491 info.setFile(serverLog);
492 if (!info.exists() || info.size() <= 0) {
493 report(Success, ki18n(
"No previous Akonadi server error log found."),
494 ki18n(
"The Akonadi server did not report any errors during its previous startup."));
496 QStandardItem *item = report(Error, ki18n(
"Previous Akonadi server error log found."),
497 ki18n(
"The Akonadi server reported errors during its previous startup. The log can be found in %1.").subs(makeLink(serverLog)));
498 item->
setData(serverLog, FileIncludeRole);
502 void SelfTestDialog::testControlLog()
507 if (!info.exists() || info.size() <= 0) {
508 report(Success, ki18n(
"No current Akonadi control error log found."),
509 ki18n(
"The Akonadi control process did not report any errors during its current startup."));
511 QStandardItem *item = report(Error, ki18n(
"Current Akonadi control error log found."),
512 ki18n(
"The Akonadi control process reported errors during its current startup. The log can be found in %1.").subs(makeLink(controlLog)));
513 item->
setData(controlLog, FileIncludeRole);
517 info.setFile(controlLog);
518 if (!info.exists() || info.size() <= 0) {
519 report(Success, ki18n(
"No previous Akonadi control error log found."),
520 ki18n(
"The Akonadi control process did not report any errors during its previous startup."));
522 QStandardItem *item = report(Error, ki18n(
"Previous Akonadi control error log found."),
523 ki18n(
"The Akonadi control process reported errors during its previous startup. The log can be found in %1.").subs(makeLink(controlLog)));
524 item->
setData(controlLog, FileIncludeRole);
528 void SelfTestDialog::testRootUser()
531 if (user.isSuperUser()) {
532 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."));
534 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."));
538 QString SelfTestDialog::createReport()
542 s <<
"Akonadi Server Self-Test Report" << endl;
543 s <<
"===============================" << endl;
545 for (
int i = 0; i < mTestModel->
rowCount(); ++i) {
548 s <<
"Test " << (i + 1) <<
": ";
549 switch (item->
data(ResultTypeRole).
toInt()) {
564 s << endl <<
"--------" << endl;
567 s <<
"Details: " << item->
data(DetailsRole).
toString() << endl;
572 if (f.open(QFile::ReadOnly)) {
573 s <<
"File content of '" << fileName <<
"':" << endl;
574 s << f.readAll() << endl;
576 s <<
"File '" << fileName <<
"' could not be opened" << endl;
583 s <<
"Directory list is empty." << endl;
585 foreach (
const QString &path, pathList) {
586 s <<
"Directory listing of '" << path <<
"':" << endl;
588 dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
589 foreach (
const QString &entry, dir.entryList()) {
597 const QByteArray envVarValue = qgetenv(envVarName);
598 s <<
"Environment variable " << envVarName <<
" is set to '" << envVarValue <<
"'" << endl;
607 void SelfTestDialog::saveReport()
612 const QString fileName = KFileDialog::getSaveFileName(
QUrl(defaultFileName),
QString(),
this,
613 i18n(
"Save Test Report"), KFileDialog::ConfirmOverwrite);
618 QFile file(fileName);
619 if (!file.open(QFile::ReadWrite)) {
620 KMessageBox::error(
this, i18n(
"Could not open file '%1'", fileName));
624 file.write(createReport().toUtf8());
628 void SelfTestDialog::copyReport()
630 #ifndef QT_NO_CLIPBOARD
635 void SelfTestDialog::linkActivated(
const QString &link)
637 KRun::runUrl(KUrl::fromPath(link),
QLatin1String(
"text/plain"),
this);
642 #include "moc_selftestdialog_p.cpp"
QByteArray toByteArray() const
QSqlError lastError() const
void setIcon(const QIcon &icon)
void setUserName(const QString &name)
QString toString(Qt::DateFormat format) const
bool contains(const QString &str, Qt::CaseSensitivity cs) const
static ServerManager * self()
Returns the singleton instance of this class, for connecting to its signals.
QSqlDatabase addDatabase(const QString &type, const QString &connectionName)
void hideIntroduction()
Hides the label with the introduction message.
static QString serviceName(ServiceType serviceType)
Returns the namespaced D-Bus service name for serviceType.
QString join(const QString &separator) const
virtual void setData(const QVariant &value, int role)
QString fromLocal8Bit(const char *str, int size)
A representation of an agent type.
QString fromUtf8(const char *str, int size)
int toInt(bool *ok) const
QStringList capabilities() const
Returns the list of supported capabilities of the agent type.
SelfTestDialog(QWidget *parent=0)
Creates a new self test dialog.
QStandardItem * item(int row, int column) const
bool contains(QChar ch, Qt::CaseSensitivity cs) const
void setHostName(const QString &host)
QByteArray toLatin1() const
QStringList toStringList() const
QVariant data(int role) const
AgentType::List types() const
Returns the list of all available agent types.
virtual int rowCount(const QModelIndex &parent) const
static AgentManager * self()
Returns the global instance of the agent manager.
void setPassword(const QString &password)
QString fromLatin1(const char *str, int size)
State
Enum for the various states the server can be in.
void setText(const QString &text, Mode mode)
void setDatabaseName(const QString &name)
void setWhatsThis(const QString &whatsThis)
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QByteArray readAllStandardOutput()
void appendRow(const QList< QStandardItem * > &items)
virtual QVariant data(int role) const
void setEditable(bool editable)
void start(const QString &program, const QStringList &arguments, QFlags< QIODevice::OpenModeFlag > mode)
QByteArray readAllStandardError()
bool waitForFinished(int msecs)