Akonadi

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

KDE's Doxygen guidelines are available online.