Akonadi

selftestdialog.cpp
1 /*
2  Copyright (c) 2008 Volker Krause <[email protected]>
3 
4  This library is free software; you can redistribute it and/or modify it
5  under the terms of the GNU Library General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or (at your
7  option) any later version.
8 
9  This library is distributed in the hope that it will be useful, but WITHOUT
10  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12  License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to the
16  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  02110-1301, USA.
18 */
19 
20 #include "selftestdialog.h"
21 #include "agentmanager.h"
22 #include "servermanager.h"
23 #include "servermanager_p.h"
24 #include "private/standarddirs_p.h"
25 #include "private/protocol_p.h"
26 
27 #include <QUrl>
28 #include <QIcon>
29 #include <QFileDialog>
30 #include <KLocalizedString>
31 #include <QMessageBox>
32 #include <QStandardPaths>
33 #include <QSqlDatabase>
34 #include <QSqlError>
35 #include <KUser>
36 
37 #include <QFileInfo>
38 #include <QProcess>
39 #include <QSettings>
40 #include <QTextStream>
41 #include <QDBusConnection>
42 #include <QDBusConnectionInterface>
43 #include <QApplication>
44 #include <QClipboard>
45 #include <QStandardItemModel>
46 #include <QDesktopServices>
47 #include <QDialogButtonBox>
48 #include <QPushButton>
49 #include <QVBoxLayout>
50 #include <QDate>
51 
52 // @cond PRIVATE
53 
54 using namespace Akonadi;
55 
56 static QString makeLink(const QString &file)
57 {
58  return QStringLiteral("<a href=\"%1\">%2</a>").arg(file, file);
59 }
60 
61 enum SelfTestRole {
62  ResultTypeRole = Qt::UserRole,
63  FileIncludeRole,
64  ListDirectoryRole,
65  EnvVarRole,
66  SummaryRole,
67  DetailsRole
68 };
69 
71  : QDialog(parent)
72 {
73  setWindowTitle(i18nc("@title:window", "Akonadi Server Self-Test"));
74  QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
75  QWidget *mainWidget = new QWidget(this);
76  QVBoxLayout *mainLayout = new QVBoxLayout(this);
77  mainLayout->addWidget(mainWidget);
78  QPushButton *user1Button = new QPushButton(this);
79  buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole);
80  QPushButton *user2Button = new QPushButton(this);
81  buttonBox->addButton(user2Button, QDialogButtonBox::ActionRole);
82  connect(buttonBox, &QDialogButtonBox::rejected, this, &SelfTestDialog::reject);
83  mainLayout->addWidget(buttonBox);
84  user1Button->setText(i18n("Save Report..."));
85  user1Button->setIcon(QIcon::fromTheme(QStringLiteral("document-save")));
86  user2Button->setText(i18n("Copy Report to Clipboard"));
87  user2Button->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
88  ui.setupUi(mainWidget);
89 
90  mTestModel = new QStandardItemModel(this);
91  ui.testView->setModel(mTestModel);
92  connect(ui.testView->selectionModel(), &QItemSelectionModel::currentChanged,
93  this, &SelfTestDialog::selectionChanged);
94  connect(ui.detailsLabel, &QLabel::linkActivated, this, &SelfTestDialog::linkActivated);
95 
96  connect(user1Button, &QPushButton::clicked, this, &SelfTestDialog::saveReport);
97  connect(user2Button, &QPushButton::clicked, this, &SelfTestDialog::copyReport);
98 
99  connect(ServerManager::self(), &ServerManager::stateChanged, this, &SelfTestDialog::runTests);
100  runTests();
101 }
102 
104 {
105  ui.introductionLabel->hide();
106 }
107 
108 QStandardItem *SelfTestDialog::report(ResultType type, const KLocalizedString &summary, const KLocalizedString &details)
109 {
110  QStandardItem *item = new QStandardItem(summary.toString());
111  switch (type) {
112  case Skip:
113  item->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok")));
114  break;
115  case Success:
116  item->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")));
117  break;
118  case Warning:
119  item->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning")));
120  break;
121  case Error:
122  item->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error")));
123  break;
124  }
125  item->setEditable(false);
126  item->setWhatsThis(details.toString());
127  item->setData(type, ResultTypeRole);
128  item->setData(summary.toString(nullptr), SummaryRole);
129  item->setData(details.toString(nullptr), DetailsRole);
130  mTestModel->appendRow(item);
131  return item;
132 }
133 
134 void SelfTestDialog::selectionChanged(const QModelIndex &index)
135 {
136  if (index.isValid()) {
137  ui.detailsLabel->setText(index.data(Qt::WhatsThisRole).toString());
138  ui.detailsGroup->setEnabled(true);
139  } else {
140  ui.detailsLabel->setText(QString());
141  ui.detailsGroup->setEnabled(false);
142  }
143 }
144 
145 void SelfTestDialog::runTests()
146 {
147  mTestModel->clear();
148 
149  const QString driver = serverSetting(QStringLiteral("General"), "Driver", QStringLiteral("QMYSQL")).toString();
150  testSQLDriver();
151  if (driver == QLatin1String("QPSQL")) {
152  testPSQLServer();
153  } else {
154 #ifndef Q_OS_WIN
155  testRootUser();
156 #endif
157  testMySQLServer();
158  testMySQLServerLog();
159  testMySQLServerConfig();
160  }
161  testAkonadiCtl();
162  testServerStatus();
163  testProtocolVersion();
164  testResources();
165  testServerLog();
166  testControlLog();
167 }
168 
169 QVariant SelfTestDialog::serverSetting(const QString &group, const char *key, const QVariant &def) const
170 {
171  const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadOnly);
172  QSettings settings(serverConfigFile, QSettings::IniFormat);
173  settings.beginGroup(group);
174  return settings.value(QString::fromLatin1(key), def);
175 }
176 
177 bool SelfTestDialog::useStandaloneMysqlServer() const
178 {
179  const QString driver = serverSetting(QStringLiteral("General"), "Driver", QStringLiteral("QMYSQL")).toString();
180  if (driver != QLatin1String("QMYSQL")) {
181  return false;
182  }
183  const bool startServer = serverSetting(driver, "StartServer", true).toBool();
184  if (!startServer) {
185  return false;
186  }
187  return true;
188 }
189 
190 bool SelfTestDialog::runProcess(const QString &app, const QStringList &args, QString &result) const
191 {
192  QProcess proc;
193  proc.start(app, args);
194  const bool rv = proc.waitForFinished();
195  result.clear();
198  return rv;
199 }
200 
201 void SelfTestDialog::testSQLDriver()
202 {
203  const QString driver = serverSetting(QStringLiteral("General"), "Driver", QStringLiteral("QMYSQL")).toString();
204  const QStringList availableDrivers = QSqlDatabase::drivers();
205  const KLocalizedString detailsOk = ki18n("The QtSQL driver '%1' is required by your current Akonadi server configuration and was found on your system.")
206  .subs(driver);
207  const KLocalizedString detailsFail = ki18n("The QtSQL driver '%1' is required by your current Akonadi server configuration.\n"
208  "The following drivers are installed: %2.\n"
209  "Make sure the required driver is installed.")
210  .subs(driver)
211  .subs(availableDrivers.join(QLatin1String(", ")));
212  QStandardItem *item = nullptr;
213  if (availableDrivers.contains(driver)) {
214  item = report(Success, ki18n("Database driver found."), detailsOk);
215  } else {
216  item = report(Error, ki18n("Database driver not found."), detailsFail);
217  }
218  item->setData(StandardDirs::serverConfigFile(StandardDirs::ReadOnly), FileIncludeRole);
219 }
220 
221 void SelfTestDialog::testMySQLServer()
222 {
223  if (!useStandaloneMysqlServer()) {
224  report(Skip, ki18n("MySQL server executable not tested."),
225  ki18n("The current configuration does not require an internal MySQL server."));
226  return;
227  }
228 
229  const QString driver = serverSetting(QStringLiteral("General"), "Driver", QStringLiteral("QMYSQL")).toString();
230  const QString serverPath = serverSetting(driver, "ServerPath", QString()).toString(); // ### default?
231 
232  const KLocalizedString details = ki18n("You have currently configured Akonadi to use the MySQL server '%1'.\n"
233  "Make sure you have the MySQL server installed, set the correct path and ensure you have the "
234  "necessary read and execution rights on the server executable. The server executable is typically "
235  "called 'mysqld'; its location varies depending on the distribution.").subs(serverPath);
236 
237  QFileInfo info(serverPath);
238  if (!info.exists()) {
239  report(Error, ki18n("MySQL server not found."), details);
240  } else if (!info.isReadable()) {
241  report(Error, ki18n("MySQL server not readable."), details);
242  } else if (!info.isExecutable()) {
243  report(Error, ki18n("MySQL server not executable."), details);
244  } else if (!serverPath.contains(QLatin1String("mysqld"))) {
245  report(Warning, ki18n("MySQL found with unexpected name."), details);
246  } else {
247  report(Success, ki18n("MySQL server found."), details);
248  }
249 
250  // be extra sure and get the server version while we are at it
251  QString result;
252  if (runProcess(serverPath, QStringList() << QStringLiteral("--version"), result)) {
253  const KLocalizedString details = ki18n("MySQL server found: %1").subs(result);
254  report(Success, ki18n("MySQL server is executable."), details);
255  } else {
256  const KLocalizedString details = ki18n("Executing the MySQL server '%1' failed with the following error message: '%2'")
257  .subs(serverPath).subs(result);
258  report(Error, ki18n("Executing the MySQL server failed."), details);
259  }
260 }
261 
262 void SelfTestDialog::testMySQLServerLog()
263 {
264  if (!useStandaloneMysqlServer()) {
265  report(Skip, ki18n("MySQL server error log not tested."),
266  ki18n("The current configuration does not require an internal MySQL server."));
267  return;
268  }
269 
270  const QString logFileName = StandardDirs::saveDir("data", QStringLiteral("db_data"))
271  + QLatin1String("/mysql.err");
272  const QFileInfo logFileInfo(logFileName);
273  if (!logFileInfo.exists() || logFileInfo.size() == 0) {
274  report(Success, ki18n("No current MySQL error log found."),
275  ki18n("The MySQL server did not report any errors during this startup. The log can be found in '%1'.").subs(logFileName));
276  return;
277  }
278  QFile logFile(logFileName);
279  if (!logFile.open(QFile::ReadOnly | QFile::Text)) {
280  report(Error, ki18n("MySQL error log not readable."),
281  ki18n("A MySQL server error log file was found but is not readable: %1").subs(makeLink(logFileName)));
282  return;
283  }
284  bool warningsFound = false;
285  QStandardItem *item = nullptr;
286  while (!logFile.atEnd()) {
287  const QString line = QString::fromUtf8(logFile.readLine());
288  if (line.contains(QLatin1String("error"), Qt::CaseInsensitive)) {
289  item = report(Error, ki18n("MySQL server log contains errors."),
290  ki18n("The MySQL server error log file '%1' contains errors.").subs(makeLink(logFileName)));
291  item->setData(logFileName, FileIncludeRole);
292  return;
293  }
294  if (!warningsFound && line.contains(QLatin1String("warn"), Qt::CaseInsensitive)) {
295  warningsFound = true;
296  }
297  }
298  if (warningsFound) {
299  item = report(Warning, ki18n("MySQL server log contains warnings."),
300  ki18n("The MySQL server log file '%1' contains warnings.").subs(makeLink(logFileName)));
301  } else {
302  item = report(Success, ki18n("MySQL server log contains no errors."),
303  ki18n("The MySQL server log file '%1' does not contain any errors or warnings.")
304  .subs(makeLink(logFileName)));
305  }
306  item->setData(logFileName, FileIncludeRole);
307 
308  logFile.close();
309 }
310 
311 void SelfTestDialog::testMySQLServerConfig()
312 {
313  if (!useStandaloneMysqlServer()) {
314  report(Skip, ki18n("MySQL server configuration not tested."),
315  ki18n("The current configuration does not require an internal MySQL server."));
316  return;
317  }
318 
319  QStandardItem *item = nullptr;
320  const QString globalConfig = StandardDirs::locateResourceFile("config", QStringLiteral("mysql-global.conf"));
321  const QFileInfo globalConfigInfo(globalConfig);
322  if (!globalConfig.isEmpty() && globalConfigInfo.exists() && globalConfigInfo.isReadable()) {
323  item = report(Success, ki18n("MySQL server default configuration found."),
324  ki18n("The default configuration for the MySQL server was found and is readable at %1.")
325  .subs(makeLink(globalConfig)));
326  item->setData(globalConfig, FileIncludeRole);
327  } else {
328  report(Error, ki18n("MySQL server default configuration not found."),
329  ki18n("The default configuration for the MySQL server was not found or was not readable. "
330  "Check your Akonadi installation is complete and you have all required access rights."));
331  }
332 
333  const QString localConfig = StandardDirs::locateResourceFile("config", QStringLiteral("mysql-local.conf"));
334  const QFileInfo localConfigInfo(localConfig);
335  if (localConfig.isEmpty() || !localConfigInfo.exists()) {
336  report(Skip, ki18n("MySQL server custom configuration not available."),
337  ki18n("The custom configuration for the MySQL server was not found but is optional."));
338  } else if (localConfigInfo.exists() && localConfigInfo.isReadable()) {
339  item = report(Success, ki18n("MySQL server custom configuration found."),
340  ki18n("The custom configuration for the MySQL server was found and is readable at %1")
341  .subs(makeLink(localConfig)));
342  item->setData(localConfig, FileIncludeRole);
343  } else {
344  report(Error, ki18n("MySQL server custom configuration not readable."),
345  ki18n("The custom configuration for the MySQL server was found at %1 but is not readable. "
346  "Check your access rights.").subs(makeLink(localConfig)));
347  }
348 
349  const QString actualConfig = StandardDirs::saveDir("data") + QStringLiteral("/mysql.conf");
350  const QFileInfo actualConfigInfo(actualConfig);
351  if (actualConfig.isEmpty() || !actualConfigInfo.exists() || !actualConfigInfo.isReadable()) {
352  report(Error, ki18n("MySQL server configuration not found or not readable."),
353  ki18n("The MySQL server configuration was not found or is not readable."));
354  } else {
355  item = report(Success, ki18n("MySQL server configuration is usable."),
356  ki18n("The MySQL server configuration was found at %1 and is readable.").subs(makeLink(actualConfig)));
357  item->setData(actualConfig, FileIncludeRole);
358  }
359 }
360 
361 void SelfTestDialog::testPSQLServer()
362 {
363  const QString dbname = serverSetting(QStringLiteral("QPSQL"), "Name", QStringLiteral("akonadi")).toString();
364  const QString hostname = serverSetting(QStringLiteral("QPSQL"), "Host", QStringLiteral("localhost")).toString();
365  const QString username = serverSetting(QStringLiteral("QPSQL"), "User", QString()).toString();
366  const QString password = serverSetting(QStringLiteral("QPSQL"), "Password", QString()).toString();
367  const int port = serverSetting(QStringLiteral("QPSQL"), "Port", 5432).toInt();
368 
369  QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QPSQL"));
370  db.setHostName(hostname);
371  db.setDatabaseName(dbname);
372 
373  if (!username.isEmpty()) {
374  db.setUserName(username);
375  }
376 
377  if (!password.isEmpty()) {
378  db.setPassword(password);
379  }
380 
381  db.setPort(port);
382 
383  if (!db.open()) {
384  const KLocalizedString details = ki18n(db.lastError().text().toLatin1().constData());
385  report(Error, ki18n("Cannot connect to PostgreSQL server."), details);
386  } else {
387  report(Success, ki18n("PostgreSQL server found."),
388  ki18n("The PostgreSQL server was found and connection is working."));
389  }
390  db.close();
391 }
392 
393 void SelfTestDialog::testAkonadiCtl()
394 {
395  const QString path = Akonadi::StandardDirs::findExecutable(QStringLiteral("akonadictl"));
396  if (path.isEmpty()) {
397  report(Error, ki18n("akonadictl not found"),
398  ki18n("The program 'akonadictl' needs to be accessible in $PATH. "
399  "Make sure you have the Akonadi server installed."));
400  return;
401  }
402  QString result;
403  if (runProcess(path, QStringList() << QStringLiteral("--version"), result)) {
404  report(Success, ki18n("akonadictl found and usable"),
405  ki18n("The program '%1' to control the Akonadi server was found "
406  "and could be executed successfully.\nResult:\n%2").subs(path).subs(result));
407  } else {
408  report(Error, ki18n("akonadictl found but not usable"),
409  ki18n("The program '%1' to control the Akonadi server was found "
410  "but could not be executed successfully.\nResult:\n%2\n"
411  "Make sure the Akonadi server is installed correctly.").subs(path).subs(result));
412  }
413 }
414 
415 void SelfTestDialog::testServerStatus()
416 {
417  if (QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Control))) {
418  report(Success, ki18n("Akonadi control process registered at D-Bus."),
419  ki18n("The Akonadi control process is registered at D-Bus which typically indicates it is operational."));
420  } else {
421  report(Error, ki18n("Akonadi control process not registered at D-Bus."),
422  ki18n("The Akonadi control process is not registered at D-Bus which typically means it was not started "
423  "or encountered a fatal error during startup."));
424  }
425 
426  if (QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Server))) {
427  report(Success, ki18n("Akonadi server process registered at D-Bus."),
428  ki18n("The Akonadi server process is registered at D-Bus which typically indicates it is operational."));
429  } else {
430  report(Error, ki18n("Akonadi server process not registered at D-Bus."),
431  ki18n("The Akonadi server process is not registered at D-Bus which typically means it was not started "
432  "or encountered a fatal error during startup."));
433  }
434 }
435 
436 void SelfTestDialog::testProtocolVersion()
437 {
438  if (Internal::serverProtocolVersion() < 0) {
439  report(Skip, ki18n("Protocol version check not possible."),
440  ki18n("Without a connection to the server it is not possible to check if the protocol version meets the requirements."));
441  return;
442  }
443  if (Internal::serverProtocolVersion() < Protocol::version()) {
444  report(Error, ki18n("Server protocol version is too old."),
445  ki18n("The server protocol version is %1, but version %2 is required by the client. "
446  "If you recently updated KDE PIM, please make sure to restart both Akonadi and KDE PIM applications.")
447  .subs(Internal::serverProtocolVersion())
448  .subs(Protocol::version()));
449  } else if (Internal::serverProtocolVersion() > Protocol::version()) {
450  report(Error, ki18n("Server protocol version is too new."),
451  ki18n("The server protocol version is %1, but version %2 is required by the client. "
452  "If you recently updated KDE PIM, please make sure to restart both Akonadi and KDE PIM applications.")
453  .subs(Internal::serverProtocolVersion())
454  .subs(Protocol::version()));
455  } else {
456  report(Success, ki18n("Server protocol version matches."),
457  ki18n("The current Protocol version is %1.")
458  .subs(Internal::serverProtocolVersion()));
459  }
460 }
461 
462 void SelfTestDialog::testResources()
463 {
464  const AgentType::List agentTypes = AgentManager::self()->types();
465  bool resourceFound = false;
466  for (const AgentType &type : agentTypes) {
467  if (type.capabilities().contains(QLatin1String("Resource"))) {
468  resourceFound = true;
469  break;
470  }
471  }
472 
473  const auto pathList = StandardDirs::locateAllResourceDirs(QStringLiteral("akonadi/agents"));
474  QStandardItem *item = nullptr;
475  if (resourceFound) {
476  item = report(Success, ki18n("Resource agents found."), ki18n("At least one resource agent has been found."));
477  } else {
478  item = report(Error, ki18n("No resource agents found."),
479  ki18n("No resource agents have been found, Akonadi is not usable without at least one. "
480  "This usually means that no resource agents are installed or that there is a setup problem. "
481  "The following paths have been searched: '%1'. "
482  "The XDG_DATA_DIRS environment variable is set to '%2'; make sure this includes all paths "
483  "where Akonadi agents are installed.")
484  .subs(pathList.join(QLatin1Char(' ')))
485  .subs(QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS"))));
486  }
487  item->setData(pathList, ListDirectoryRole);
488  item->setData(QByteArray("XDG_DATA_DIRS"), EnvVarRole);
489 }
490 
491 void SelfTestDialog::testServerLog()
492 {
493  QString serverLog = StandardDirs::saveDir("data") + QLatin1String("/akonadiserver.error");
494  QFileInfo info(serverLog);
495  if (!info.exists() || info.size() <= 0) {
496  report(Success, ki18n("No current Akonadi server error log found."),
497  ki18n("The Akonadi server did not report any errors during its current startup."));
498  } else {
499  QStandardItem *item = report(Error, ki18n("Current Akonadi server error log found."),
500  ki18n("The Akonadi server reported errors during its current startup. The log can be found in %1.").subs(makeLink(serverLog)));
501  item->setData(serverLog, FileIncludeRole);
502  }
503 
504  serverLog += QStringLiteral(".old");
505  info.setFile(serverLog);
506  if (!info.exists() || info.size() <= 0) {
507  report(Success, ki18n("No previous Akonadi server error log found."),
508  ki18n("The Akonadi server did not report any errors during its previous startup."));
509  } else {
510  QStandardItem *item = report(Error, ki18n("Previous Akonadi server error log found."),
511  ki18n("The Akonadi server reported errors during its previous startup. The log can be found in %1.").subs(makeLink(serverLog)));
512  item->setData(serverLog, FileIncludeRole);
513  }
514 }
515 
516 void SelfTestDialog::testControlLog()
517 {
518  QString controlLog = StandardDirs::saveDir("data") + QLatin1String("/akonadi_control.error");
519  QFileInfo info(controlLog);
520  if (!info.exists() || info.size() <= 0) {
521  report(Success, ki18n("No current Akonadi control error log found."),
522  ki18n("The Akonadi control process did not report any errors during its current startup."));
523  } else {
524  QStandardItem *item = report(Error, ki18n("Current Akonadi control error log found."),
525  ki18n("The Akonadi control process reported errors during its current startup. The log can be found in %1.").subs(makeLink(controlLog)));
526  item->setData(controlLog, FileIncludeRole);
527  }
528 
529  controlLog += QStringLiteral(".old");
530  info.setFile(controlLog);
531  if (!info.exists() || info.size() <= 0) {
532  report(Success, ki18n("No previous Akonadi control error log found."),
533  ki18n("The Akonadi control process did not report any errors during its previous startup."));
534  } else {
535  QStandardItem *item = report(Error, ki18n("Previous Akonadi control error log found."),
536  ki18n("The Akonadi control process reported errors during its previous startup. The log can be found in %1.").subs(makeLink(controlLog)));
537  item->setData(controlLog, FileIncludeRole);
538  }
539 }
540 
541 void SelfTestDialog::testRootUser()
542 {
543  KUser user;
544  if (user.isSuperUser()) {
545  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."));
546  } else {
547  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."));
548  }
549 }
550 
551 QString SelfTestDialog::createReport()
552 {
553  QString result;
554  QTextStream s(&result);
555  s << "Akonadi Server Self-Test Report";
556  s << "===============================";
557 
558  for (int i = 0; i < mTestModel->rowCount(); ++i) {
559  QStandardItem *item = mTestModel->item(i);
560  s << '\n';
561  s << "Test " << (i + 1) << ": ";
562  switch (item->data(ResultTypeRole).toInt()) {
563  case Skip:
564  s << "SKIP";
565  break;
566  case Success:
567  s << "SUCCESS";
568  break;
569  case Warning:
570  s << "WARNING";
571  break;
572  case Error:
573  default:
574  s << "ERROR";
575  break;
576  }
577  s << "\n--------\n";
578  s << '\n';
579  s << item->data(SummaryRole).toString() << '\n';
580  s << "Details: " << item->data(DetailsRole).toString() << '\n';
581  if (item->data(FileIncludeRole).isValid()) {
582  s << '\n';
583  const QString fileName = item->data(FileIncludeRole).toString();
584  QFile f(fileName);
585  if (f.open(QFile::ReadOnly)) {
586  s << "File content of '" << fileName << "':" << '\n';
587  s << f.readAll() << '\n';
588  } else {
589  s << "File '" << fileName << "' could not be opened\n";
590  }
591  }
592  if (item->data(ListDirectoryRole).isValid()) {
593  s << '\n';
594  const QStringList pathList = item->data(ListDirectoryRole).toStringList();
595  if (pathList.isEmpty()) {
596  s << "Directory list is empty.\n";
597  }
598  for (const QString &path : pathList) {
599  s << "Directory listing of '" << path << "':\n";
600  QDir dir(path);
601  dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
602  const QStringList listEntries(dir.entryList());
603  for (const QString &entry : listEntries) {
604  s << entry << '\n';
605  }
606  }
607  }
608  if (item->data(EnvVarRole).isValid()) {
609  s << '\n';
610  const QByteArray envVarName = item->data(EnvVarRole).toByteArray();
611  const QByteArray envVarValue = qgetenv(envVarName.constData());
612  s << "Environment variable " << envVarName << " is set to '" << envVarValue << "'\n";
613  }
614  }
615 
616  s << '\n';
617  s.flush();
618  return result;
619 }
620 
621 void SelfTestDialog::saveReport()
622 {
623  const QString defaultFileName = QStringLiteral("akonadi-selftest-report-")
624  + QDate::currentDate().toString(QStringLiteral("yyyyMMdd"))
625  + QStringLiteral(".txt");
626  const QString fileName = QFileDialog::getSaveFileName(this, i18n("Save Test Report"), defaultFileName);
627  if (fileName.isEmpty()) {
628  return;
629  }
630 
631  QFile file(fileName);
632  if (!file.open(QFile::ReadWrite)) {
633  QMessageBox::critical(this, i18n("Error"), i18n("Could not open file '%1'", fileName));
634  return;
635  }
636 
637  file.write(createReport().toUtf8());
638  file.close();
639 }
640 
641 void SelfTestDialog::copyReport()
642 {
643 #ifndef QT_NO_CLIPBOARD
644  QApplication::clipboard()->setText(createReport());
645 #endif
646 }
647 
648 void SelfTestDialog::linkActivated(const QString &link)
649 {
651 }
652 
653 // @endcond
SelfTestDialog(QWidget *parent=nullptr)
Creates a new self test dialog.
QByteArray toByteArray() const const
QSqlError lastError() const const
virtual void reject()
void setIcon(const QIcon &icon)
QString toString() const
void setUserName(const QString &name)
QStringList drivers()
QString toString(Qt::DateFormat format) const const
QVector< AgentType > List
Describes a list of agent types.
void addButton(QAbstractButton *button, QDialogButtonBox::ButtonRole role)
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
static ServerManager * self()
Returns the singleton instance of this class, for connecting to its signals.
void setPort(int port)
QSqlDatabase addDatabase(const QString &type, const QString &connectionName)
void hideIntroduction()
Hides the label with the introduction message.
QDBusConnection sessionBus()
static QString serviceName(ServiceType serviceType)
Returns the namespaced D-Bus service name for serviceType.
QString join(const QString &separator) const const
void setIcon(const QIcon &icon)
virtual void setData(const QVariant &value, int role)
KLocalizedString subs(int a, int fieldWidth=0, int base=10, QChar fillChar=QLatin1Char(' ')) const
void clear()
bool isValid() const const
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
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 const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
int toInt(bool *ok, int base) const const
bool isEmpty() const const
bool isEmpty() const const
void clicked(bool checked)
const char * constData() const const
QVector< T > result()
Returns the result of this SELECT query.
void linkActivated(const QString &link)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
void setHostName(const QString &host)
void setText(const QString &text)
QString i18n(const char *text, const TYPE &arg...)
void currentChanged(const QModelIndex &current, const QModelIndex &previous)
QByteArray toLatin1() const const
QStringList toStringList() const const
QVariant data(int role) const const
KIOFILEWIDGETS_EXPORT QString dir(const QString &fileClass)
NETWORKMANAGERQT_EXPORT QString hostname()
AgentType::List types() const
Returns the list of all available agent types.
Helper integration between Akonadi and Qt.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QDate currentDate()
bool isSuperUser() const
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
QMessageBox::StandardButton critical(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
static AgentManager * self()
Returns the global instance of the agent manager.
void setPassword(const QString &password)
void setText(const QString &text)
KLocalizedString KI18N_EXPORT ki18n(const char *text)
QString fromLatin1(const char *str, int size)
bool isValid() const const
QIcon fromTheme(const QString &name)
void setText(const QString &text, QClipboard::Mode mode)
void stateChanged(Akonadi::ServerManager::State state)
Emitted whenever the server state changes.
QString text() const const
void setDatabaseName(const QString &name)
bool openUrl(const QUrl &url)
void setWhatsThis(const QString &whatsThis)
QString toString() const const
QClipboard * clipboard()
QByteArray readAllStandardOutput()
virtual QVariant data(int role) const const
void setEditable(bool editable)
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
QByteArray readAllStandardError()
QUrl fromLocalFile(const QString &localFile)
bool waitForFinished(int msecs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Wed May 27 2020 22:43:40 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.