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 | |
52 | using namespace Akonadi; |
53 | |
54 | static QString makeLink(const QString &file) |
55 | { |
56 | return QString::fromLatin1("<a href=\"%1\">%2</a>" ).arg(file, file); |
57 | } |
58 | |
59 | enum SelfTestRole { |
60 | ResultTypeRole = Qt::UserRole, |
61 | FileIncludeRole, |
62 | ListDirectoryRole, |
63 | EnvVarRole, |
64 | SummaryRole, |
65 | DetailsRole |
66 | }; |
67 | |
68 | SelfTestDialog::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 | |
93 | void SelfTestDialog::hideIntroduction() |
94 | { |
95 | ui.introductionLabel->hide(); |
96 | } |
97 | |
98 | QStandardItem *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 | |
124 | void 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 | |
135 | void 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 | |
159 | QVariant 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 | |
167 | bool 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 | |
180 | bool 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 | |
191 | void 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 | |
211 | void 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 | |
252 | void 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 | |
301 | void 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 | |
351 | void 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 | |
383 | void 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 | |
405 | void 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 | |
426 | void 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 | |
447 | void 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 | |
476 | void 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 | |
502 | void 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 | |
528 | void 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 | |
538 | QString 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 | |
607 | void 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 | |
628 | void SelfTestDialog::copyReport() |
629 | { |
630 | #ifndef QT_NO_CLIPBOARD |
631 | QApplication::clipboard()->setText(createReport()); |
632 | #endif |
633 | } |
634 | |
635 | void 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 | |