File indexing completed on 2024-06-16 04:50:38
0001 /* 0002 SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "selftestdialog.h" 0008 #include "agentmanager.h" 0009 #include "private/protocol_p.h" 0010 #include "private/standarddirs_p.h" 0011 #include "servermanager.h" 0012 #include "servermanager_p.h" 0013 0014 #include <KLocalizedString> 0015 #include <KUser> 0016 #include <QFileDialog> 0017 #include <QIcon> 0018 #include <QMessageBox> 0019 #include <QSqlDatabase> 0020 #include <QSqlError> 0021 #include <QStandardPaths> 0022 #include <QUrl> 0023 0024 #include <QApplication> 0025 #include <QClipboard> 0026 #include <QDBusConnection> 0027 #include <QDBusConnectionInterface> 0028 #include <QDate> 0029 #include <QDesktopServices> 0030 #include <QDialogButtonBox> 0031 #include <QFileInfo> 0032 #include <QProcess> 0033 #include <QPushButton> 0034 #include <QSettings> 0035 #include <QStandardItemModel> 0036 #include <QTextStream> 0037 #include <QVBoxLayout> 0038 0039 /// @cond PRIVATE 0040 0041 using namespace Akonadi; 0042 0043 static QString makeLink(const QString &file) 0044 { 0045 return QStringLiteral("<a href=\"%1\">%2</a>").arg(file, file); 0046 } 0047 0048 enum SelfTestRole { 0049 ResultTypeRole = Qt::UserRole, 0050 FileIncludeRole, 0051 ListDirectoryRole, 0052 EnvVarRole, 0053 SummaryRole, 0054 DetailsRole, 0055 }; 0056 0057 SelfTestDialog::SelfTestDialog(QWidget *parent) 0058 : QDialog(parent) 0059 { 0060 setWindowTitle(i18nc("@title:window", "Akonadi Server Self-Test")); 0061 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this); 0062 auto mainWidget = new QWidget(this); 0063 auto mainLayout = new QVBoxLayout(this); 0064 mainLayout->addWidget(mainWidget); 0065 auto user1Button = new QPushButton(this); 0066 buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole); 0067 auto user2Button = new QPushButton(this); 0068 buttonBox->addButton(user2Button, QDialogButtonBox::ActionRole); 0069 connect(buttonBox, &QDialogButtonBox::rejected, this, &SelfTestDialog::reject); 0070 mainLayout->addWidget(buttonBox); 0071 user1Button->setText(i18n("Save Report...")); 0072 user1Button->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); 0073 user2Button->setText(i18n("Copy Report to Clipboard")); 0074 user2Button->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); 0075 ui.setupUi(mainWidget); 0076 0077 mTestModel = new QStandardItemModel(this); 0078 ui.testView->setModel(mTestModel); 0079 connect(ui.testView->selectionModel(), &QItemSelectionModel::currentChanged, this, &SelfTestDialog::selectionChanged); 0080 connect(ui.detailsLabel, &QLabel::linkActivated, this, &SelfTestDialog::linkActivated); 0081 0082 connect(user1Button, &QPushButton::clicked, this, &SelfTestDialog::saveReport); 0083 connect(user2Button, &QPushButton::clicked, this, &SelfTestDialog::copyReport); 0084 0085 connect(ServerManager::self(), &ServerManager::stateChanged, this, &SelfTestDialog::runTests); 0086 runTests(); 0087 } 0088 0089 void SelfTestDialog::hideIntroduction() 0090 { 0091 ui.introductionLabel->hide(); 0092 } 0093 0094 QStandardItem *SelfTestDialog::report(ResultType type, const KLocalizedString &summary, const KLocalizedString &details) 0095 { 0096 auto item = new QStandardItem(summary.toString()); 0097 switch (type) { 0098 case Skip: 0099 item->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok"))); 0100 break; 0101 case Success: 0102 item->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); 0103 break; 0104 case Warning: 0105 item->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); 0106 break; 0107 case Error: 0108 item->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); 0109 break; 0110 } 0111 item->setEditable(false); 0112 item->setWhatsThis(details.toString()); 0113 item->setData(type, ResultTypeRole); 0114 item->setData(summary.toString(nullptr), SummaryRole); 0115 item->setData(details.toString(nullptr), DetailsRole); 0116 mTestModel->appendRow(item); 0117 return item; 0118 } 0119 0120 void SelfTestDialog::selectionChanged(const QModelIndex &index) 0121 { 0122 if (index.isValid()) { 0123 ui.detailsLabel->setText(index.data(Qt::WhatsThisRole).toString()); 0124 ui.detailsGroup->setEnabled(true); 0125 } else { 0126 ui.detailsLabel->setText(QString()); 0127 ui.detailsGroup->setEnabled(false); 0128 } 0129 } 0130 0131 void SelfTestDialog::runTests() 0132 { 0133 mTestModel->clear(); 0134 0135 const QString driver = serverSetting(QStringLiteral("General"), "Driver", QStringLiteral("QMYSQL")).toString(); 0136 testSQLDriver(); 0137 if (driver == QLatin1StringView("QPSQL")) { 0138 testPSQLServer(); 0139 } else { 0140 #ifndef Q_OS_WIN 0141 testRootUser(); 0142 #endif 0143 testMySQLServer(); 0144 testMySQLServerLog(); 0145 testMySQLServerConfig(); 0146 } 0147 testAkonadiCtl(); 0148 testServerStatus(); 0149 testProtocolVersion(); 0150 testResources(); 0151 testServerLog(); 0152 testControlLog(); 0153 } 0154 0155 QVariant SelfTestDialog::serverSetting(const QString &group, const char *key, const QVariant &def) const 0156 { 0157 const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadOnly); 0158 QSettings settings(serverConfigFile, QSettings::IniFormat); 0159 settings.beginGroup(group); 0160 return settings.value(QString::fromLatin1(key), def); 0161 } 0162 0163 bool SelfTestDialog::useStandaloneMysqlServer() const 0164 { 0165 const QString driver = serverSetting(QStringLiteral("General"), "Driver", QStringLiteral("QMYSQL")).toString(); 0166 if (driver != QLatin1StringView("QMYSQL")) { 0167 return false; 0168 } 0169 const bool startServer = serverSetting(driver, "StartServer", true).toBool(); 0170 return startServer; 0171 } 0172 0173 bool SelfTestDialog::runProcess(const QString &app, const QStringList &args, QString &result) const 0174 { 0175 QProcess proc; 0176 proc.start(app, args); 0177 const bool rv = proc.waitForFinished(); 0178 result.clear(); 0179 result = QString::fromLocal8Bit(proc.readAllStandardError()); 0180 result += QString::fromLocal8Bit(proc.readAllStandardOutput()); 0181 return rv; 0182 } 0183 0184 void SelfTestDialog::testSQLDriver() 0185 { 0186 const QString driver = serverSetting(QStringLiteral("General"), "Driver", QStringLiteral("QMYSQL")).toString(); 0187 const QStringList availableDrivers = QSqlDatabase::drivers(); 0188 const KLocalizedString detailsOk = 0189 ki18n("The QtSQL driver '%1' is required by your current Akonadi server configuration and was found on your system.").subs(driver); 0190 const KLocalizedString detailsFail = ki18n( 0191 "The QtSQL driver '%1' is required by your current Akonadi server configuration.\n" 0192 "The following drivers are installed: %2.\n" 0193 "Make sure the required driver is installed.") 0194 .subs(driver) 0195 .subs(availableDrivers.join(QLatin1StringView(", "))); 0196 QStandardItem *item = nullptr; 0197 if (availableDrivers.contains(driver)) { 0198 item = report(Success, ki18n("Database driver found."), detailsOk); 0199 } else { 0200 item = report(Error, ki18n("Database driver not found."), detailsFail); 0201 } 0202 item->setData(StandardDirs::serverConfigFile(StandardDirs::ReadOnly), FileIncludeRole); 0203 } 0204 0205 void SelfTestDialog::testMySQLServer() 0206 { 0207 if (!useStandaloneMysqlServer()) { 0208 report(Skip, ki18n("MySQL server executable not tested."), ki18n("The current configuration does not require an internal MySQL server.")); 0209 return; 0210 } 0211 0212 const QString driver = serverSetting(QStringLiteral("General"), "Driver", QStringLiteral("QMYSQL")).toString(); 0213 const QString serverPath = serverSetting(driver, "ServerPath", QString()).toString(); // ### default? 0214 0215 const KLocalizedString details = ki18n( 0216 "You have currently configured Akonadi to use the MySQL server '%1'.\n" 0217 "Make sure you have the MySQL server installed, set the correct path and ensure you have the " 0218 "necessary read and execution rights on the server executable. The server executable is typically " 0219 "called 'mysqld'; its location varies depending on the distribution.") 0220 .subs(serverPath); 0221 0222 QFileInfo info(serverPath); 0223 if (!info.exists()) { 0224 report(Error, ki18n("MySQL server not found."), details); 0225 } else if (!info.isReadable()) { 0226 report(Error, ki18n("MySQL server not readable."), details); 0227 } else if (!info.isExecutable()) { 0228 report(Error, ki18n("MySQL server not executable."), details); 0229 } else if (!serverPath.contains(QLatin1StringView("mysqld"))) { 0230 report(Warning, ki18n("MySQL found with unexpected name."), details); 0231 } else { 0232 report(Success, ki18n("MySQL server found."), details); 0233 } 0234 0235 // be extra sure and get the server version while we are at it 0236 QString result; 0237 if (runProcess(serverPath, QStringList() << QStringLiteral("--version"), result)) { 0238 const KLocalizedString details = ki18n("MySQL server found: %1").subs(result); 0239 report(Success, ki18n("MySQL server is executable."), details); 0240 } else { 0241 const KLocalizedString details = ki18n("Executing the MySQL server '%1' failed with the following error message: '%2'").subs(serverPath).subs(result); 0242 report(Error, ki18n("Executing the MySQL server failed."), details); 0243 } 0244 } 0245 0246 void SelfTestDialog::testMySQLServerLog() 0247 { 0248 if (!useStandaloneMysqlServer()) { 0249 report(Skip, ki18n("MySQL server error log not tested."), ki18n("The current configuration does not require an internal MySQL server.")); 0250 return; 0251 } 0252 0253 const QString logFileName = StandardDirs::saveDir("data", QStringLiteral("db_data")) + QLatin1StringView("/mysql.err"); 0254 const QFileInfo logFileInfo(logFileName); 0255 if (!logFileInfo.exists() || logFileInfo.size() == 0) { 0256 report(Success, 0257 ki18n("No current MySQL error log found."), 0258 ki18n("The MySQL server did not report any errors during this startup. The log can be found in '%1'.").subs(logFileName)); 0259 return; 0260 } 0261 QFile logFile(logFileName); 0262 if (!logFile.open(QFile::ReadOnly | QFile::Text)) { 0263 report(Error, 0264 ki18n("MySQL error log not readable."), 0265 ki18n("A MySQL server error log file was found but is not readable: %1").subs(makeLink(logFileName))); 0266 return; 0267 } 0268 bool warningsFound = false; 0269 QStandardItem *item = nullptr; 0270 while (!logFile.atEnd()) { 0271 const QString line = QString::fromUtf8(logFile.readLine()); 0272 if (line.contains(QLatin1StringView("error"), Qt::CaseInsensitive)) { 0273 item = report(Error, 0274 ki18n("MySQL server log contains errors."), 0275 ki18n("The MySQL server error log file '%1' contains errors.").subs(makeLink(logFileName))); 0276 item->setData(logFileName, FileIncludeRole); 0277 return; 0278 } 0279 if (!warningsFound && line.contains(QLatin1StringView("warn"), Qt::CaseInsensitive)) { 0280 warningsFound = true; 0281 } 0282 } 0283 if (warningsFound) { 0284 item = report(Warning, 0285 ki18n("MySQL server log contains warnings."), 0286 ki18n("The MySQL server log file '%1' contains warnings.").subs(makeLink(logFileName))); 0287 } else { 0288 item = report(Success, 0289 ki18n("MySQL server log contains no errors."), 0290 ki18n("The MySQL server log file '%1' does not contain any errors or warnings.").subs(makeLink(logFileName))); 0291 } 0292 item->setData(logFileName, FileIncludeRole); 0293 0294 logFile.close(); 0295 } 0296 0297 void SelfTestDialog::testMySQLServerConfig() 0298 { 0299 if (!useStandaloneMysqlServer()) { 0300 report(Skip, ki18n("MySQL server configuration not tested."), ki18n("The current configuration does not require an internal MySQL server.")); 0301 return; 0302 } 0303 0304 QStandardItem *item = nullptr; 0305 const QString globalConfig = StandardDirs::locateResourceFile("config", QStringLiteral("mysql-global.conf")); 0306 const QFileInfo globalConfigInfo(globalConfig); 0307 if (!globalConfig.isEmpty() && globalConfigInfo.exists() && globalConfigInfo.isReadable()) { 0308 item = report(Success, 0309 ki18n("MySQL server default configuration found."), 0310 ki18n("The default configuration for the MySQL server was found and is readable at %1.").subs(makeLink(globalConfig))); 0311 item->setData(globalConfig, FileIncludeRole); 0312 } else { 0313 report(Error, 0314 ki18n("MySQL server default configuration not found."), 0315 ki18n("The default configuration for the MySQL server was not found or was not readable. " 0316 "Check your Akonadi installation is complete and you have all required access rights.")); 0317 } 0318 0319 const QString localConfig = StandardDirs::locateResourceFile("config", QStringLiteral("mysql-local.conf")); 0320 const QFileInfo localConfigInfo(localConfig); 0321 if (localConfig.isEmpty() || !localConfigInfo.exists()) { 0322 report(Skip, 0323 ki18n("MySQL server custom configuration not available."), 0324 ki18n("The custom configuration for the MySQL server was not found but is optional.")); 0325 } else if (localConfigInfo.exists() && localConfigInfo.isReadable()) { 0326 item = report(Success, 0327 ki18n("MySQL server custom configuration found."), 0328 ki18n("The custom configuration for the MySQL server was found and is readable at %1").subs(makeLink(localConfig))); 0329 item->setData(localConfig, FileIncludeRole); 0330 } else { 0331 report(Error, 0332 ki18n("MySQL server custom configuration not readable."), 0333 ki18n("The custom configuration for the MySQL server was found at %1 but is not readable. " 0334 "Check your access rights.") 0335 .subs(makeLink(localConfig))); 0336 } 0337 0338 const QString actualConfig = StandardDirs::saveDir("data") + QStringLiteral("/mysql.conf"); 0339 const QFileInfo actualConfigInfo(actualConfig); 0340 if (actualConfig.isEmpty() || !actualConfigInfo.exists() || !actualConfigInfo.isReadable()) { 0341 report(Error, 0342 ki18n("MySQL server configuration not found or not readable."), 0343 ki18n("The MySQL server configuration was not found or is not readable.")); 0344 } else { 0345 item = report(Success, 0346 ki18n("MySQL server configuration is usable."), 0347 ki18n("The MySQL server configuration was found at %1 and is readable.").subs(makeLink(actualConfig))); 0348 item->setData(actualConfig, FileIncludeRole); 0349 } 0350 } 0351 0352 void SelfTestDialog::testPSQLServer() 0353 { 0354 const QString dbname = serverSetting(QStringLiteral("QPSQL"), "Name", QStringLiteral("akonadi")).toString(); 0355 const QString hostname = serverSetting(QStringLiteral("QPSQL"), "Host", QStringLiteral("localhost")).toString(); 0356 const QString username = serverSetting(QStringLiteral("QPSQL"), "User", QString()).toString(); 0357 const QString password = serverSetting(QStringLiteral("QPSQL"), "Password", QString()).toString(); 0358 const int port = serverSetting(QStringLiteral("QPSQL"), "Port", 5432).toInt(); 0359 0360 QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QPSQL")); 0361 db.setHostName(hostname); 0362 db.setDatabaseName(dbname); 0363 0364 if (!username.isEmpty()) { 0365 db.setUserName(username); 0366 } 0367 0368 if (!password.isEmpty()) { 0369 db.setPassword(password); 0370 } 0371 0372 db.setPort(port); 0373 0374 if (!db.open()) { 0375 const KLocalizedString details = ki18n(db.lastError().text().toLatin1().constData()); 0376 report(Error, ki18n("Cannot connect to PostgreSQL server."), details); 0377 } else { 0378 report(Success, ki18n("PostgreSQL server found."), ki18n("The PostgreSQL server was found and connection is working.")); 0379 } 0380 db.close(); 0381 } 0382 0383 void SelfTestDialog::testAkonadiCtl() 0384 { 0385 const QString path = Akonadi::StandardDirs::findExecutable(QStringLiteral("akonadictl")); 0386 if (path.isEmpty()) { 0387 report(Error, 0388 ki18n("akonadictl not found"), 0389 ki18n("The program 'akonadictl' needs to be accessible in $PATH. " 0390 "Make sure you have the Akonadi server installed.")); 0391 return; 0392 } 0393 QString result; 0394 if (runProcess(path, QStringList() << QStringLiteral("--version"), result)) { 0395 report(Success, 0396 ki18n("akonadictl found and usable"), 0397 ki18n("The program '%1' to control the Akonadi server was found " 0398 "and could be executed successfully.\nResult:\n%2") 0399 .subs(path) 0400 .subs(result)); 0401 } else { 0402 report(Error, 0403 ki18n("akonadictl found but not usable"), 0404 ki18n("The program '%1' to control the Akonadi server was found " 0405 "but could not be executed successfully.\nResult:\n%2\n" 0406 "Make sure the Akonadi server is installed correctly.") 0407 .subs(path) 0408 .subs(result)); 0409 } 0410 } 0411 0412 void SelfTestDialog::testServerStatus() 0413 { 0414 if (QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Control))) { 0415 report(Success, 0416 ki18n("Akonadi control process registered at D-Bus."), 0417 ki18n("The Akonadi control process is registered at D-Bus which typically indicates it is operational.")); 0418 } else { 0419 report(Error, 0420 ki18n("Akonadi control process not registered at D-Bus."), 0421 ki18n("The Akonadi control process is not registered at D-Bus which typically means it was not started " 0422 "or encountered a fatal error during startup.")); 0423 } 0424 0425 if (QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Server))) { 0426 report(Success, 0427 ki18n("Akonadi server process registered at D-Bus."), 0428 ki18n("The Akonadi server process is registered at D-Bus which typically indicates it is operational.")); 0429 } else { 0430 report(Error, 0431 ki18n("Akonadi server process not registered at D-Bus."), 0432 ki18n("The Akonadi server process is not registered at D-Bus which typically means it was not started " 0433 "or encountered a fatal error during startup.")); 0434 } 0435 } 0436 0437 void SelfTestDialog::testProtocolVersion() 0438 { 0439 if (Internal::serverProtocolVersion() < 0) { 0440 report(Skip, 0441 ki18n("Protocol version check not possible."), 0442 ki18n("Without a connection to the server it is not possible to check if the protocol version meets the requirements.")); 0443 return; 0444 } 0445 if (Internal::serverProtocolVersion() < Protocol::version()) { 0446 report(Error, 0447 ki18n("Server protocol version is too old."), 0448 ki18n("The server protocol version is %1, but version %2 is required by the client. " 0449 "If you recently updated KDE PIM, please make sure to restart both Akonadi and KDE PIM applications.") 0450 .subs(Internal::serverProtocolVersion()) 0451 .subs(Protocol::version())); 0452 } else if (Internal::serverProtocolVersion() > Protocol::version()) { 0453 report(Error, 0454 ki18n("Server protocol version is too new."), 0455 ki18n("The server protocol version is %1, but version %2 is required by the client. " 0456 "If you recently updated KDE PIM, please make sure to restart both Akonadi and KDE PIM applications.") 0457 .subs(Internal::serverProtocolVersion()) 0458 .subs(Protocol::version())); 0459 } else { 0460 report(Success, ki18n("Server protocol version matches."), ki18n("The current Protocol version is %1.").subs(Internal::serverProtocolVersion())); 0461 } 0462 } 0463 0464 void SelfTestDialog::testResources() 0465 { 0466 const AgentType::List agentTypes = AgentManager::self()->types(); 0467 bool resourceFound = false; 0468 for (const AgentType &type : agentTypes) { 0469 if (type.capabilities().contains(QLatin1StringView("Resource"))) { 0470 resourceFound = true; 0471 break; 0472 } 0473 } 0474 0475 const auto pathList = StandardDirs::locateAllResourceDirs(QStringLiteral("akonadi/agents")); 0476 QStandardItem *item = nullptr; 0477 if (resourceFound) { 0478 item = report(Success, ki18n("Resource agents found."), ki18n("At least one resource agent has been found.")); 0479 } else { 0480 item = report(Error, 0481 ki18n("No resource agents found."), 0482 ki18n("No resource agents have been found, Akonadi is not usable without at least one. " 0483 "This usually means that no resource agents are installed or that there is a setup problem. " 0484 "The following paths have been searched: '%1'. " 0485 "The XDG_DATA_DIRS environment variable is set to '%2'; make sure this includes all paths " 0486 "where Akonadi agents are installed.") 0487 .subs(pathList.join(QLatin1Char(' '))) 0488 .subs(QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS")))); 0489 } 0490 item->setData(pathList, ListDirectoryRole); 0491 item->setData(QByteArray("XDG_DATA_DIRS"), EnvVarRole); 0492 } 0493 0494 void SelfTestDialog::testServerLog() 0495 { 0496 QString serverLog = StandardDirs::saveDir("data") + QLatin1StringView("/akonadiserver.error"); 0497 QFileInfo info(serverLog); 0498 if (!info.exists() || info.size() <= 0) { 0499 report(Success, ki18n("No current Akonadi server error log found."), ki18n("The Akonadi server did not report any errors during its current startup.")); 0500 } else { 0501 QStandardItem *item = 0502 report(Error, 0503 ki18n("Current Akonadi server error log found."), 0504 ki18n("The Akonadi server reported errors during its current startup. The log can be found in %1.").subs(makeLink(serverLog))); 0505 item->setData(serverLog, FileIncludeRole); 0506 } 0507 0508 serverLog += QStringLiteral(".old"); 0509 info.setFile(serverLog); 0510 if (!info.exists() || info.size() <= 0) { 0511 report(Success, 0512 ki18n("No previous Akonadi server error log found."), 0513 ki18n("The Akonadi server did not report any errors during its previous startup.")); 0514 } else { 0515 QStandardItem *item = 0516 report(Error, 0517 ki18n("Previous Akonadi server error log found."), 0518 ki18n("The Akonadi server reported errors during its previous startup. The log can be found in %1.").subs(makeLink(serverLog))); 0519 item->setData(serverLog, FileIncludeRole); 0520 } 0521 } 0522 0523 void SelfTestDialog::testControlLog() 0524 { 0525 QString controlLog = StandardDirs::saveDir("data") + QLatin1StringView("/akonadi_control.error"); 0526 QFileInfo info(controlLog); 0527 if (!info.exists() || info.size() <= 0) { 0528 report(Success, 0529 ki18n("No current Akonadi control error log found."), 0530 ki18n("The Akonadi control process did not report any errors during its current startup.")); 0531 } else { 0532 QStandardItem *item = 0533 report(Error, 0534 ki18n("Current Akonadi control error log found."), 0535 ki18n("The Akonadi control process reported errors during its current startup. The log can be found in %1.").subs(makeLink(controlLog))); 0536 item->setData(controlLog, FileIncludeRole); 0537 } 0538 0539 controlLog += QStringLiteral(".old"); 0540 info.setFile(controlLog); 0541 if (!info.exists() || info.size() <= 0) { 0542 report(Success, 0543 ki18n("No previous Akonadi control error log found."), 0544 ki18n("The Akonadi control process did not report any errors during its previous startup.")); 0545 } else { 0546 QStandardItem *item = 0547 report(Error, 0548 ki18n("Previous Akonadi control error log found."), 0549 ki18n("The Akonadi control process reported errors during its previous startup. The log can be found in %1.").subs(makeLink(controlLog))); 0550 item->setData(controlLog, FileIncludeRole); 0551 } 0552 } 0553 0554 void SelfTestDialog::testRootUser() 0555 { 0556 KUser user; 0557 if (user.isSuperUser()) { 0558 report(Error, 0559 ki18n("Akonadi was started as root"), 0560 ki18n("Running Internet-facing applications as root/administrator exposes you to many security risks. MySQL, used by this Akonadi installation, " 0561 "will not allow itself to run as root, to protect you from these risks.")); 0562 } else { 0563 report(Success, 0564 ki18n("Akonadi is not running as root"), 0565 ki18n("Akonadi is not running as a root/administrator user, which is the recommended setup for a secure system.")); 0566 } 0567 } 0568 0569 QString SelfTestDialog::createReport() 0570 { 0571 QString result; 0572 QTextStream s(&result); 0573 s << "Akonadi Server Self-Test Report"; 0574 s << "==============================="; 0575 0576 for (int i = 0; i < mTestModel->rowCount(); ++i) { 0577 QStandardItem *item = mTestModel->item(i); 0578 s << '\n'; 0579 s << "Test " << (i + 1) << ": "; 0580 switch (item->data(ResultTypeRole).toInt()) { 0581 case Skip: 0582 s << "SKIP"; 0583 break; 0584 case Success: 0585 s << "SUCCESS"; 0586 break; 0587 case Warning: 0588 s << "WARNING"; 0589 break; 0590 case Error: 0591 default: 0592 s << "ERROR"; 0593 break; 0594 } 0595 s << "\n--------\n"; 0596 s << '\n'; 0597 s << item->data(SummaryRole).toString() << '\n'; 0598 s << "Details: " << item->data(DetailsRole).toString() << '\n'; 0599 if (item->data(FileIncludeRole).isValid()) { 0600 s << '\n'; 0601 const QString fileName = item->data(FileIncludeRole).toString(); 0602 QFile f(fileName); 0603 if (f.open(QFile::ReadOnly)) { 0604 s << "File content of '" << fileName << "':" << '\n'; 0605 s << f.readAll() << '\n'; 0606 } else { 0607 s << "File '" << fileName << "' could not be opened\n"; 0608 } 0609 } 0610 if (item->data(ListDirectoryRole).isValid()) { 0611 s << '\n'; 0612 const QStringList pathList = item->data(ListDirectoryRole).toStringList(); 0613 if (pathList.isEmpty()) { 0614 s << "Directory list is empty.\n"; 0615 } 0616 for (const QString &path : pathList) { 0617 s << "Directory listing of '" << path << "':\n"; 0618 QDir dir(path); 0619 dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot); 0620 const QStringList listEntries(dir.entryList()); 0621 for (const QString &entry : listEntries) { 0622 s << entry << '\n'; 0623 } 0624 } 0625 } 0626 if (item->data(EnvVarRole).isValid()) { 0627 s << '\n'; 0628 const QByteArray envVarName = item->data(EnvVarRole).toByteArray(); 0629 const QByteArray envVarValue = qgetenv(envVarName.constData()); 0630 s << "Environment variable " << envVarName << " is set to '" << envVarValue << "'\n"; 0631 } 0632 } 0633 0634 s << '\n'; 0635 s.flush(); 0636 return result; 0637 } 0638 0639 void SelfTestDialog::saveReport() 0640 { 0641 const QString defaultFileName = 0642 QStringLiteral("akonadi-selftest-report-") + QDate::currentDate().toString(QStringLiteral("yyyyMMdd")) + QStringLiteral(".txt"); 0643 const QString fileName = QFileDialog::getSaveFileName(this, i18n("Save Test Report"), defaultFileName); 0644 if (fileName.isEmpty()) { 0645 return; 0646 } 0647 0648 QFile file(fileName); 0649 if (!file.open(QFile::ReadWrite)) { 0650 QMessageBox::critical(this, i18nc("@title:window", "Error"), i18n("Could not open file '%1'", fileName)); 0651 return; 0652 } 0653 0654 file.write(createReport().toUtf8()); 0655 file.close(); 0656 } 0657 0658 void SelfTestDialog::copyReport() 0659 { 0660 #ifndef QT_NO_CLIPBOARD 0661 QApplication::clipboard()->setText(createReport()); 0662 #endif 0663 } 0664 0665 void SelfTestDialog::linkActivated(const QString &link) 0666 { 0667 QDesktopServices::openUrl(QUrl::fromLocalFile(link)); 0668 } 0669 0670 /// @endcond 0671 0672 #include "moc_selftestdialog.cpp"