1/*
2 Copyright (c) 2008 Volker Krause <vkrause@kde.org>
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_p.h"
21#include "agentmanager.h"
22#include "dbusconnectionpool.h"
23#include "session_p.h"
24#include "servermanager.h"
25#include "servermanager_p.h"
26
27#include <akonadi/private/xdgbasedirs_p.h>
28
29#include <KDebug>
30#include <KUrl>
31#include <KIcon>
32#include <KFileDialog>
33#include <KLocalizedString>
34#include <KMessageBox>
35#include <KRun>
36#include <KStandardDirs>
37#include <KUser>
38
39#include <QtCore/QFileInfo>
40#include <QtCore/QProcess>
41#include <QtCore/QSettings>
42#include <QtCore/QTextStream>
43#include <QtDBus/QtDBus>
44#include <QApplication>
45#include <QClipboard>
46#include <QStandardItemModel>
47#include <QtSql/QSqlDatabase>
48#include <QtSql/QSqlError>
49
50// @cond PRIVATE
51
52using namespace Akonadi;
53
54static QString makeLink(const QString &file)
55{
56 return QString::fromLatin1("<a href=\"%1\">%2</a>").arg(file, file);
57}
58
59enum SelfTestRole {
60 ResultTypeRole = Qt::UserRole,
61 FileIncludeRole,
62 ListDirectoryRole,
63 EnvVarRole,
64 SummaryRole,
65 DetailsRole
66};
67
68SelfTestDialog::SelfTestDialog(QWidget *parent)
69 : KDialog(parent)
70{
71 setCaption(i18n("Akonadi Server Self-Test"));
72 setButtons(Close | User1 | User2);
73 setButtonText(User1, i18n("Save Report..."));
74 setButtonIcon(User1, KIcon(QString::fromLatin1("document-save")));
75 setButtonText(User2, i18n("Copy Report to Clipboard"));
76 setButtonIcon(User2, KIcon(QString::fromLatin1("edit-copy")));
77 showButtonSeparator(true);
78 ui.setupUi(mainWidget());
79
80 mTestModel = new QStandardItemModel(this);
81 ui.testView->setModel(mTestModel);
82 connect(ui.testView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
83 SLOT(selectionChanged(QModelIndex)));
84 connect(ui.detailsLabel, SIGNAL(linkActivated(QString)), SLOT(linkActivated(QString)));
85
86 connect(this, SIGNAL(user1Clicked()), SLOT(saveReport()));
87 connect(this, SIGNAL(user2Clicked()), SLOT(copyReport()));
88
89 connect(ServerManager::self(), SIGNAL(stateChanged(Akonadi::ServerManager::State)), SLOT(runTests()));
90 runTests();
91}
92
93void SelfTestDialog::hideIntroduction()
94{
95 ui.introductionLabel->hide();
96}
97
98QStandardItem *SelfTestDialog::report(ResultType type, const KLocalizedString &summary, const KLocalizedString &details)
99{
100 QStandardItem *item = new QStandardItem(summary.toString());
101 switch (type) {
102 case Skip:
103 item->setIcon(KIcon(QString::fromLatin1("dialog-ok")));
104 break;
105 case Success:
106 item->setIcon(KIcon(QString::fromLatin1("dialog-ok-apply")));
107 break;
108 case Warning:
109 item->setIcon(KIcon(QString::fromLatin1("dialog-warning")));
110 break;
111 case Error:
112 default:
113 item->setIcon(KIcon(QString::fromLatin1("dialog-error")));
114 }
115 item->setEditable(false);
116 item->setWhatsThis(details.toString());
117 item->setData(type, ResultTypeRole);
118 item->setData(summary.toString(0), SummaryRole);
119 item->setData(details.toString(0), DetailsRole);
120 mTestModel->appendRow(item);
121 return item;
122}
123
124void SelfTestDialog::selectionChanged(const QModelIndex &index)
125{
126 if (index.isValid()) {
127 ui.detailsLabel->setText(index.data(Qt::WhatsThisRole).toString());
128 ui.detailsGroup->setEnabled(true);
129 } else {
130 ui.detailsLabel->setText(QString());
131 ui.detailsGroup->setEnabled(false);
132 }
133}
134
135void SelfTestDialog::runTests()
136{
137 mTestModel->clear();
138
139 const QString driver = serverSetting(QLatin1String("General"), "Driver", QLatin1String("QMYSQL")).toString();
140 testSQLDriver();
141 if (driver == QLatin1String("QPSQL")) {
142 testPSQLServer();
143 } else {
144#ifndef Q_OS_WIN
145 testRootUser();
146#endif
147 testMySQLServer();
148 testMySQLServerLog();
149 testMySQLServerConfig();
150 }
151 testAkonadiCtl();
152 testServerStatus();
153 testProtocolVersion();
154 testResources();
155 testServerLog();
156 testControlLog();
157}
158
159QVariant SelfTestDialog::serverSetting(const QString &group, const char *key, const QVariant &def) const
160{
161 const QString serverConfigFile = XdgBaseDirs::akonadiServerConfigFile(XdgBaseDirs::ReadWrite);
162 QSettings settings(serverConfigFile, QSettings::IniFormat);
163 settings.beginGroup(group);
164 return settings.value(QString::fromLatin1(key), def);
165}
166
167bool SelfTestDialog::useStandaloneMysqlServer() const
168{
169 const QString driver = serverSetting(QLatin1String("General"), "Driver", QLatin1String("QMYSQL")).toString();
170 if (driver != QLatin1String("QMYSQL")) {
171 return false;
172 }
173 const bool startServer = serverSetting(driver, "StartServer", true).toBool();
174 if (!startServer) {
175 return false;
176 }
177 return true;
178}
179
180bool SelfTestDialog::runProcess(const QString &app, const QStringList &args, QString &result) const
181{
182 QProcess proc;
183 proc.start(app, args);
184 const bool rv = proc.waitForFinished();
185 result.clear();
186 result += QString::fromLocal8Bit(proc.readAllStandardError());
187 result += QString::fromLocal8Bit(proc.readAllStandardOutput());
188 return rv;
189}
190
191void SelfTestDialog::testSQLDriver()
192{
193 const QString driver = serverSetting(QLatin1String("General"), "Driver", QLatin1String("QMYSQL")).toString();
194 const QStringList availableDrivers = QSqlDatabase::drivers();
195 const KLocalizedString detailsOk = ki18n("The QtSQL driver '%1' is required by your current Akonadi server configuration and was found on your system.")
196 .subs(driver);
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.")
200 .subs(driver)
201 .subs(availableDrivers.join(QLatin1String(", ")));
202 QStandardItem *item = 0;
203 if (availableDrivers.contains(driver)) {
204 item = report(Success, ki18n("Database driver found."), detailsOk);
205 } else {
206 item = report(Error, ki18n("Database driver not found."), detailsFail);
207 }
208 item->setData(XdgBaseDirs::akonadiServerConfigFile(XdgBaseDirs::ReadWrite), FileIncludeRole);
209}
210
211void SelfTestDialog::testMySQLServer()
212{
213 if (!useStandaloneMysqlServer()) {
214 report(Skip, ki18n("MySQL server executable not tested."),
215 ki18n("The current configuration does not require an internal MySQL server."));
216 return;
217 }
218
219 const QString driver = serverSetting(QLatin1String("General"), "Driver", QLatin1String("QMYSQL")).toString();
220 const QString serverPath = serverSetting(driver, "ServerPath", QLatin1String("")).toString(); // ### default?
221
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);
226
227 QFileInfo info(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);
234 } else if (!serverPath.contains(QLatin1String("mysqld"))) {
235 report(Warning, ki18n("MySQL found with unexpected name."), details);
236 } else {
237 report(Success, ki18n("MySQL server found."), details);
238 }
239
240 // be extra sure and get the server version while we are at it
241 QString result;
242 if (runProcess(serverPath, QStringList() << QLatin1String("--version"), result)) {
243 const KLocalizedString details = ki18n("MySQL server found: %1").subs(result);
244 report(Success, ki18n("MySQL server is executable."), details);
245 } else {
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);
249 }
250}
251
252void SelfTestDialog::testMySQLServerLog()
253{
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."));
257 return;
258 }
259
260 const QString logFileName = XdgBaseDirs::saveDir("data", QLatin1String("akonadi/db_data"))
261 + QDir::separator() + QString::fromLatin1("mysql.err");
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));
266 return;
267 }
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)));
272 return;
273 }
274 bool warningsFound = false;
275 QStandardItem *item = 0;
276 while (!logFile.atEnd()) {
277 const QString line = QString::fromUtf8(logFile.readLine());
278 if (line.contains(QLatin1String("error"), Qt::CaseInsensitive)) {
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);
282 return;
283 }
284 if (!warningsFound && line.contains(QLatin1String("warn"), Qt::CaseInsensitive)) {
285 warningsFound = true;
286 }
287 }
288 if (warningsFound) {
289 item = report(Warning, ki18n("MySQL server log contains warnings."),
290 ki18n("The MySQL server log file '%1' contains warnings.").subs(makeLink(logFileName)));
291 } else {
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)));
295 }
296 item->setData(logFileName, FileIncludeRole);
297
298 logFile.close();
299}
300
301void SelfTestDialog::testMySQLServerConfig()
302{
303 if (!useStandaloneMysqlServer()) {
304 report(Skip, ki18n("MySQL server configuration not tested."),
305 ki18n("The current configuration does not require an internal MySQL server."));
306 return;
307 }
308
309 QStandardItem *item = 0;
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);
317 } else {
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."));
321 }
322
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);
333 } else {
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)));
337 }
338
339 const QString actualConfig = XdgBaseDirs::saveDir("data", QLatin1String("akonadi")) + QLatin1String("/mysql.conf");
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."));
344 } else {
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);
348 }
349}
350
351void SelfTestDialog::testPSQLServer()
352{
353 const QString dbname = serverSetting(QLatin1String("QPSQL"), "Name", QLatin1String("akonadi")).toString();
354 const QString hostname = serverSetting(QLatin1String("QPSQL"), "Host", QLatin1String("localhost")).toString();
355 const QString username = serverSetting(QLatin1String("QPSQL"), "User", QString()).toString();
356 const QString password = serverSetting(QLatin1String("QPSQL"), "Password", QString()).toString();
357 const int port = serverSetting(QLatin1String("QPSQL"), "Port", 5432).toInt();
358
359 QSqlDatabase db = QSqlDatabase::addDatabase(QLatin1String("QPSQL"));
360 db.setHostName(hostname);
361 db.setDatabaseName(dbname);
362
363 if (!username.isEmpty()) {
364 db.setUserName(username);
365 }
366
367 if (!password.isEmpty()) {
368 db.setPassword(password);
369 }
370
371 db.setPort(port);
372
373 if (!db.open()) {
374 const KLocalizedString details = ki18n(db.lastError().text().toLatin1());
375 report(Error, ki18n("Cannot connect to PostgreSQL server."), details);
376 } else {
377 report(Success, ki18n("PostgreSQL server found."),
378 ki18n("The PostgreSQL server was found and connection is working."));
379 }
380 db.close();
381}
382
383void SelfTestDialog::testAkonadiCtl()
384{
385 const QString path = KStandardDirs::findExe(QLatin1String("akonadictl"));
386 if (path.isEmpty()) {
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."));
390 return;
391 }
392 QString result;
393 if (runProcess(path, QStringList() << QLatin1String("--version"), result)) {
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));
397 } else {
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));
402 }
403}
404
405void SelfTestDialog::testServerStatus()
406{
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."));
410 } else {
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."));
414 }
415
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."));
419 } else {
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."));
423 }
424}
425
426void SelfTestDialog::testProtocolVersion()
427{
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."));
431 return;
432 }
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()));
439 } else {
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()));
444 }
445}
446
447void SelfTestDialog::testResources()
448{
449 AgentType::List agentTypes = AgentManager::self()->types();
450 bool resourceFound = false;
451 foreach (const AgentType &type, agentTypes) {
452 if (type.capabilities().contains(QLatin1String("Resource"))) {
453 resourceFound = true;
454 break;
455 }
456 }
457
458 const QStringList pathList = XdgBaseDirs::findAllResourceDirs("data", QLatin1String("akonadi/agents"));
459 QStandardItem *item = 0;
460 if (resourceFound) {
461 item = report(Success, ki18n("Resource agents found."), ki18n("At least one resource agent has been found."));
462 } else {
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.")
469 .subs(pathList.join(QLatin1String(" ")))
470 .subs(QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS"))));
471 }
472 item->setData(pathList, ListDirectoryRole);
473 item->setData(QByteArray("XDG_DATA_DIRS"), EnvVarRole);
474}
475
476void Akonadi::SelfTestDialog::testServerLog()
477{
478 QString serverLog = XdgBaseDirs::saveDir("data", QLatin1String("akonadi"))
479 + QDir::separator() + QString::fromLatin1("akonadiserver.error");
480 QFileInfo info(serverLog);
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."));
484 } else {
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);
488 }
489
490 serverLog += QLatin1String(".old");
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."));
495 } else {
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);
499 }
500}
501
502void SelfTestDialog::testControlLog()
503{
504 QString controlLog = XdgBaseDirs::saveDir("data", QLatin1String("akonadi"))
505 + QDir::separator() + QString::fromLatin1("akonadi_control.error");
506 QFileInfo info(controlLog);
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."));
510 } else {
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);
514 }
515
516 controlLog += QLatin1String(".old");
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."));
521 } else {
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);
525 }
526}
527
528void SelfTestDialog::testRootUser()
529{
530 KUser user;
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."));
533 } else {
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."));
535 }
536}
537
538QString SelfTestDialog::createReport()
539{
540 QString result;
541 QTextStream s(&result);
542 s << "Akonadi Server Self-Test Report" << endl;
543 s << "===============================" << endl;
544
545 for (int i = 0; i < mTestModel->rowCount(); ++i) {
546 QStandardItem *item = mTestModel->item(i);
547 s << endl;
548 s << "Test " << (i + 1) << ": ";
549 switch (item->data(ResultTypeRole).toInt()) {
550 case Skip:
551 s << "SKIP";
552 break;
553 case Success:
554 s << "SUCCESS";
555 break;
556 case Warning:
557 s << "WARNING";
558 break;
559 case Error:
560 default:
561 s << "ERROR";
562 break;
563 }
564 s << endl << "--------" << endl;
565 s << endl;
566 s << item->data(SummaryRole).toString() << endl;
567 s << "Details: " << item->data(DetailsRole).toString() << endl;
568 if (item->data(FileIncludeRole).isValid()) {
569 s << endl;
570 const QString fileName = item->data(FileIncludeRole).toString();
571 QFile f(fileName);
572 if (f.open(QFile::ReadOnly)) {
573 s << "File content of '" << fileName << "':" << endl;
574 s << f.readAll() << endl;
575 } else {
576 s << "File '" << fileName << "' could not be opened" << endl;
577 }
578 }
579 if (item->data(ListDirectoryRole).isValid()) {
580 s << endl;
581 const QStringList pathList = item->data(ListDirectoryRole).toStringList();
582 if (pathList.isEmpty()) {
583 s << "Directory list is empty." << endl;
584 }
585 foreach (const QString &path, pathList) {
586 s << "Directory listing of '" << path << "':" << endl;
587 QDir dir(path);
588 dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
589 foreach (const QString &entry, dir.entryList()) {
590 s << entry << endl;
591 }
592 }
593 }
594 if (item->data(EnvVarRole).isValid()) {
595 s << endl;
596 const QByteArray envVarName = item->data(EnvVarRole).toByteArray();
597 const QByteArray envVarValue = qgetenv(envVarName);
598 s << "Environment variable " << envVarName << " is set to '" << envVarValue << "'" << endl;
599 }
600 }
601
602 s << endl;
603 s.flush();
604 return result;
605}
606
607void SelfTestDialog::saveReport()
608{
609 const QString defaultFileName = QLatin1String("akonadi-selftest-report-")
610 + QDate::currentDate().toString(QLatin1String("yyyyMMdd"))
611 + QLatin1String(".txt");
612 const QString fileName = KFileDialog::getSaveFileName(QUrl(defaultFileName), QString(), this,
613 i18n("Save Test Report"));
614 if (fileName.isEmpty()) {
615 return;
616 }
617
618 QFile file(fileName);
619 if (!file.open(QFile::ReadWrite)) {
620 KMessageBox::error(this, i18n("Could not open file '%1'", fileName));
621 return;
622 }
623
624 file.write(createReport().toUtf8());
625 file.close();
626}
627
628void SelfTestDialog::copyReport()
629{
630#ifndef QT_NO_CLIPBOARD
631 QApplication::clipboard()->setText(createReport());
632#endif
633}
634
635void SelfTestDialog::linkActivated(const QString &link)
636{
637 KRun::runUrl(KUrl::fromPath(link), QLatin1String("text/plain"), this);
638}
639
640// @endcond
641
642#include "moc_selftestdialog_p.cpp"
643