Akonadi

akonadi.cpp
1 /***************************************************************************
2  * Copyright (C) 2006 by Till Adam <[email protected]> *
3  * *
4  * This program is free software; you can redistribute it and/or modify *
5  * it under the terms of the GNU Library General Public License as *
6  * published by the Free Software Foundation; either version 2 of the *
7  * License, or (at your option) any later version. *
8  * *
9  * This program is distributed in the hope that it will be useful, *
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12  * GNU General Public License for more details. *
13  * *
14  * You should have received a copy of the GNU Library General Public *
15  * License along with this program; if not, write to the *
16  * Free Software Foundation, Inc., *
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
18  ***************************************************************************/
19 
20 #include "akonadi.h"
21 #include "handler.h"
22 #include "connection.h"
23 #include "serveradaptor.h"
24 #include "akonadiserver_debug.h"
25 
26 #include "cachecleaner.h"
27 #include "intervalcheck.h"
28 #include "storagejanitor.h"
29 #include "storage/dbconfig.h"
30 #include "storage/datastore.h"
31 #include "notificationmanager.h"
32 #include "resourcemanager.h"
33 #include "tracer.h"
34 #include "utils.h"
35 #include "debuginterface.h"
36 #include "storage/itemretrievalmanager.h"
37 #include "storage/collectionstatistics.h"
38 #include "preprocessormanager.h"
39 #include "search/searchmanager.h"
40 #include "search/searchtaskmanager.h"
41 #include "aklocalserver.h"
42 
43 #include <memory>
44 #include <private/standarddirs_p.h>
45 #include <private/protocol_p.h>
46 #include <private/dbus_p.h>
47 #include <private/instance_p.h>
48 
49 #include <QSqlQuery>
50 #include <QSqlError>
51 
52 #include <QCoreApplication>
53 #include <QDir>
54 #include <QSettings>
55 #include <QTimer>
56 #include <QDBusServiceWatcher>
57 
58 using namespace Akonadi;
59 using namespace Akonadi::Server;
60 
61 namespace {
62 
63 class AkonadiDataStore : public DataStore
64 {
65  Q_OBJECT
66 public:
67  AkonadiDataStore(AkonadiServer &server):
68  DataStore(server)
69  {}
70 };
71 
72 class AkonadiDataStoreFactory : public DataStoreFactory
73 {
74 public:
75  AkonadiDataStoreFactory(AkonadiServer &akonadi)
76  : DataStoreFactory()
77  , m_akonadi(akonadi)
78  {}
79 
80  DataStore *createStore() override
81  {
82  return new AkonadiDataStore(m_akonadi);
83  }
84 
85 private:
86  AkonadiServer &m_akonadi;
87 };
88 
89 }
90 
91 AkonadiServer::AkonadiServer()
92  : QObject()
93 {
94  // Register bunch of useful types
95  qRegisterMetaType<Protocol::CommandPtr>();
96  qRegisterMetaType<Protocol::ChangeNotificationPtr>();
97  qRegisterMetaType<Protocol::ChangeNotificationList>();
98  qRegisterMetaType<quintptr>("quintptr");
99 
100  DataStore::setFactory(std::make_unique<AkonadiDataStoreFactory>(*this));
101 }
102 
103 bool AkonadiServer::init()
104 {
105  qCInfo(AKONADISERVER_LOG) << "Starting up the Akonadi Server...";
106 
107  const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite);
108  QSettings settings(serverConfigFile, QSettings::IniFormat);
109  // Restrict permission to 600, as the file might contain database password in plaintext
110  QFile::setPermissions(serverConfigFile, QFile::ReadOwner | QFile::WriteOwner);
111 
112  const QString connectionSettingsFile = StandardDirs::connectionConfigFile(StandardDirs::WriteOnly);
113  QSettings connectionSettings(connectionSettingsFile, QSettings::IniFormat);
114 
115  const QByteArray dbusAddress = qgetenv("DBUS_SESSION_BUS_ADDRESS");
116  if (!dbusAddress.isEmpty()) {
117  connectionSettings.setValue(QStringLiteral("DBUS/Address"), QLatin1String(dbusAddress));
118  }
119 
120 
121  // Setup database
122  if (!setupDatabase()) {
123  quit();
124  return false;
125  }
126 
127  // Create local servers and start listening
128  if (!createServers(settings, connectionSettings)) {
129  quit();
130  return false;
131  }
132 
133  const auto searchManagers = settings.value(QStringLiteral("Search/Manager"),
134  QStringList{QStringLiteral("Agent")}).toStringList();
135 
136  mTracer = std::make_unique<Tracer>();
137  mCollectionStats = std::make_unique<CollectionStatistics>();
138  mCacheCleaner = std::make_unique<CacheCleaner>();
139  mItemRetrieval = std::make_unique<ItemRetrievalManager>();
140  mAgentSearchManager = std::make_unique<SearchTaskManager>();
141 
142  mDebugInterface = std::make_unique<DebugInterface>(*mTracer.get());
143  mResourceManager = std::make_unique<ResourceManager>(*mTracer.get());
144  mPreprocessorManager = std::make_unique<PreprocessorManager>(*mTracer.get());
145  mIntervalCheck = std::make_unique<IntervalCheck>(*mItemRetrieval.get());
146  mSearchManager = std::make_unique<SearchManager>(searchManagers, *mAgentSearchManager.get());
147  mStorageJanitor = std::make_unique<StorageJanitor>(*this);
148 
149  if (settings.value(QStringLiteral("General/DisablePreprocessing"), false).toBool()) {
150  mPreprocessorManager->setEnabled(false);
151  }
152 
153  new ServerAdaptor(this);
154  QDBusConnection::sessionBus().registerObject(QStringLiteral("/Server"), this);
155 
156  mControlWatcher = std::make_unique<QDBusServiceWatcher>(
157  DBus::serviceName(DBus::Control), QDBusConnection::sessionBus(),
158  QDBusServiceWatcher::WatchForUnregistration);
159  connect(mControlWatcher.get(), &QDBusServiceWatcher::serviceUnregistered,
160  this, [this]() {
161  qCCritical(AKONADISERVER_LOG) << "Control process died, committing suicide!";
162  quit();
163  });
164 
165  // Unhide all the items that are actually hidden.
166  // The hidden flag was probably left out after an (abrupt)
167  // server quit. We don't attempt to resume preprocessing
168  // for the items as we don't actually know at which stage the
169  // operation was interrupted...
170  DataStore::self()->unhideAllPimItems();
171 
172  // We are ready, now register org.freedesktop.Akonadi service to DBus and
173  // the fun can begin
174  if (!QDBusConnection::sessionBus().registerService(DBus::serviceName(DBus::Server))) {
175  qCCritical(AKONADISERVER_LOG) << "Unable to connect to dbus service: " << QDBusConnection::sessionBus().lastError().message();
176  quit();
177  return false;
178  }
179 
180  return true;
181 }
182 
183 AkonadiServer::~AkonadiServer() = default;
184 
185 bool AkonadiServer::quit()
186 {
187  if (mAlreadyShutdown) {
188  return true;
189  }
190  mAlreadyShutdown = true;
191 
192  qCDebug(AKONADISERVER_LOG) << "terminating connection threads";
193  mConnections.clear();
194 
195  qCDebug(AKONADISERVER_LOG) << "terminating service threads";
196  // Keep this order in sync (reversed) with the order of initialization
197  mStorageJanitor.reset();
198  mSearchManager.reset();
199  mIntervalCheck.reset();
200  mPreprocessorManager.reset();
201  mResourceManager.reset();
202  mDebugInterface.reset();
203 
204  mAgentSearchManager.reset();
205  mItemRetrieval.reset();
206  mCacheCleaner.reset();
207  mCollectionStats.reset();
208  mTracer.reset();
209 
210  if (DbConfig::isConfigured()) {
211  if (DataStore::hasDataStore()) {
212  DataStore::self()->close();
213  }
214  qCDebug(AKONADISERVER_LOG) << "stopping db process";
215  stopDatabaseProcess();
216  }
217 
218  //QSettings settings(StandardDirs::serverConfigFile(), QSettings::IniFormat);
219  const QString connectionSettingsFile = StandardDirs::connectionConfigFile(StandardDirs::WriteOnly);
220 
221  if (!QDir::home().remove(connectionSettingsFile)) {
222  qCCritical(AKONADISERVER_LOG) << "Failed to remove runtime connection config file";
223  }
224 
225  QTimer::singleShot(0, this, &AkonadiServer::doQuit);
226 
227  return true;
228 }
229 
230 void AkonadiServer::doQuit()
231 {
233 }
234 
235 void AkonadiServer::newCmdConnection(quintptr socketDescriptor)
236 {
237  if (mAlreadyShutdown) {
238  return;
239  }
240 
241  auto connection = std::make_unique<Connection>(socketDescriptor, *this);
242  connect(connection.get(), &Connection::disconnected,
243  this, &AkonadiServer::connectionDisconnected);
244  mConnections.push_back(std::move(connection));
245 }
246 
247 void AkonadiServer::connectionDisconnected()
248 {
249  auto it = std::find_if(mConnections.begin(), mConnections.end(),
250  [this](const auto &ptr) { return ptr.get() == sender(); });
251  Q_ASSERT(it != mConnections.end());
252  mConnections.erase(it);
253 }
254 
255 bool AkonadiServer::setupDatabase()
256 {
257  if (!DbConfig::configuredDatabase()) {
258  return false;
259  }
260 
261  if (DbConfig::configuredDatabase()->useInternalServer()) {
262  if (!startDatabaseProcess()) {
263  return false;
264  }
265  } else {
266  if (!createDatabase()) {
267  return false;
268  }
269  }
270 
271  DbConfig::configuredDatabase()->setup();
272 
273  // initialize the database
274  DataStore *db = DataStore::self();
275  if (!db->database().isOpen()) {
276  qCCritical(AKONADISERVER_LOG) << "Unable to open database" << db->database().lastError().text();
277  return false;
278  }
279  if (!db->init()) {
280  qCCritical(AKONADISERVER_LOG) << "Unable to initialize database.";
281  return false;
282  }
283 
284  return true;
285 }
286 
287 bool AkonadiServer::startDatabaseProcess()
288 {
289  if (!DbConfig::configuredDatabase()->useInternalServer()) {
290  qCCritical(AKONADISERVER_LOG) << "Trying to start external database!";
291  }
292 
293  // create the database directories if they don't exists
294  StandardDirs::saveDir("data");
295  StandardDirs::saveDir("data", QStringLiteral("file_db_data"));
296 
297  return DbConfig::configuredDatabase()->startInternalServer();
298 }
299 
300 bool AkonadiServer::createDatabase()
301 {
302  bool success = true;
303  const QLatin1String initCon("initConnection");
304  QSqlDatabase db = QSqlDatabase::addDatabase(DbConfig::configuredDatabase()->driverName(), initCon);
305  DbConfig::configuredDatabase()->apply(db);
306  db.setDatabaseName(DbConfig::configuredDatabase()->databaseName());
307  if (!db.isValid()) {
308  qCCritical(AKONADISERVER_LOG) << "Invalid database object during initial database connection";
309  return false;
310  }
311 
312  if (db.open()) {
313  db.close();
314  } else {
315  qCCritical(AKONADISERVER_LOG) << "Failed to use database" << DbConfig::configuredDatabase()->databaseName();
316  qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
317  qCDebug(AKONADISERVER_LOG) << "Trying to create database now...";
318 
319  db.close();
320  db.setDatabaseName(QString());
321  if (db.open()) {
322  {
323  QSqlQuery query(db);
324  if (!query.exec(QStringLiteral("CREATE DATABASE %1").arg(DbConfig::configuredDatabase()->databaseName()))) {
325  qCCritical(AKONADISERVER_LOG) << "Failed to create database";
326  qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text();
327  qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
328  success = false;
329  }
330  } // make sure query is destroyed before we close the db
331  db.close();
332  } else {
333  qCCritical(AKONADISERVER_LOG) << "Failed to connect to database!";
334  qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
335  success = false;
336  }
337  }
338  return success;
339 }
340 
341 void AkonadiServer::stopDatabaseProcess()
342 {
343  if (!DbConfig::configuredDatabase()->useInternalServer()) {
344  // closing initConnection this late to work around QTBUG-63108
345  QSqlDatabase::removeDatabase(QStringLiteral("initConnection"));
346  return;
347  }
348 
349  DbConfig::configuredDatabase()->stopInternalServer();
350 }
351 
352 bool AkonadiServer::createServers(QSettings &settings, QSettings &connectionSettings)
353 {
354  mCmdServer = std::make_unique<AkLocalServer>(this);
355  connect(mCmdServer.get(), QOverload<quintptr>::of(&AkLocalServer::newConnection), this, &AkonadiServer::newCmdConnection);
356 
357  mNotificationManager = std::make_unique<NotificationManager>();
358  mNtfServer = std::make_unique<AkLocalServer>(this);
359  // Note: this is a queued connection, as NotificationManager lives in its
360  // own thread
361  connect(mNtfServer.get(), QOverload<quintptr>::of(&AkLocalServer::newConnection),
362  mNotificationManager.get(), &NotificationManager::registerConnection);
363 
364  // TODO: share socket setup with client
365 #ifdef Q_OS_WIN
366  // use the installation prefix as uid
367  QString suffix;
368  if (Instance::hasIdentifier()) {
369  suffix = QStringLiteral("%1-").arg(Instance::identifier());
370  }
371  suffix += QString::fromUtf8(QUrl::toPercentEncoding(qApp->applicationDirPath()));
372  const QString defaultCmdPipe = QStringLiteral("Akonadi-Cmd-") % suffix;
373  const QString cmdPipe = settings.value(QStringLiteral("Connection/NamedPipe"), defaultCmdPipe).toString();
374  if (!mCmdServer->listen(cmdPipe)) {
375  qCCritical(AKONADISERVER_LOG) << "Unable to listen on Named Pipe" << cmdPipe << ":" << mCmdServer->errorString();
376  return false;
377  }
378 
379  const QString defaultNtfPipe = QStringLiteral("Akonadi-Ntf-") % suffix;
380  const QString ntfPipe = settings.value(QStringLiteral("Connection/NtfNamedPipe"), defaultNtfPipe).toString();
381  if (!mNtfServer->listen(ntfPipe)) {
382  qCCritical(AKONADISERVER_LOG) << "Unable to listen on Named Pipe" << ntfPipe << ":" << mNtfServer->errorString();
383  return false;
384  }
385 
386  connectionSettings.setValue(QStringLiteral("Data/Method"), QStringLiteral("NamedPipe"));
387  connectionSettings.setValue(QStringLiteral("Data/NamedPipe"), cmdPipe);
388  connectionSettings.setValue(QStringLiteral("Notifications/Method"), QStringLiteral("NamedPipe"));
389  connectionSettings.setValue(QStringLiteral("Notifications/NamedPipe"), ntfPipe);
390 #else
391  Q_UNUSED(settings);
392 
393  const QString cmdSocketName = QStringLiteral("akonadiserver-cmd.socket");
394  const QString ntfSocketName = QStringLiteral("akonadiserver-ntf.socket");
395  const QString socketDir = Utils::preferredSocketDirectory(StandardDirs::saveDir("data"),
396  qMax(cmdSocketName.length(), ntfSocketName.length()));
397  const QString cmdSocketFile = socketDir % QLatin1Char('/') % cmdSocketName;
398  QFile::remove(cmdSocketFile);
399  if (!mCmdServer->listen(cmdSocketFile)) {
400  qCCritical(AKONADISERVER_LOG) << "Unable to listen on Unix socket" << cmdSocketFile << ":" << mCmdServer->errorString();
401  return false;
402  }
403 
404  const QString ntfSocketFile = socketDir % QLatin1Char('/') % ntfSocketName;
405  QFile::remove(ntfSocketFile);
406  if (!mNtfServer->listen(ntfSocketFile)) {
407  qCCritical(AKONADISERVER_LOG) << "Unable to listen on Unix socket" << ntfSocketFile << ":" << mNtfServer->errorString();
408  return false;
409  }
410 
411  connectionSettings.setValue(QStringLiteral("Data/Method"), QStringLiteral("UnixPath"));
412  connectionSettings.setValue(QStringLiteral("Data/UnixPath"), cmdSocketFile);
413  connectionSettings.setValue(QStringLiteral("Notifications/Method"), QStringLiteral("UnixPath"));
414  connectionSettings.setValue(QStringLiteral("Notifications/UnixPath"), ntfSocketFile);
415 #endif
416 
417  return true;
418 }
419 
420 CacheCleaner *AkonadiServer::cacheCleaner()
421 {
422  return mCacheCleaner.get();
423 }
424 
425 IntervalCheck &AkonadiServer::intervalChecker()
426 {
427  return *mIntervalCheck.get();
428 }
429 
430 ResourceManager &AkonadiServer::resourceManager()
431 {
432  return *mResourceManager.get();
433 }
434 
435 NotificationManager *AkonadiServer::notificationManager()
436 {
437  return mNotificationManager.get();
438 }
439 
440 CollectionStatistics &AkonadiServer::collectionStatistics()
441 {
442  return *mCollectionStats.get();
443 }
444 
445 PreprocessorManager &AkonadiServer::preprocessorManager()
446 {
447  return *mPreprocessorManager.get();
448 }
449 
450 SearchTaskManager &AkonadiServer::agentSearchManager()
451 {
452  return *mAgentSearchManager.get();
453 }
454 
455 SearchManager &AkonadiServer::searchManager()
456 {
457  return *mSearchManager.get();
458 }
459 
460 ItemRetrievalManager &AkonadiServer::itemRetrievalManager()
461 {
462  return *mItemRetrieval.get();
463 }
464 
465 Tracer &AkonadiServer::tracer()
466 {
467  return *mTracer.get();
468 }
469 
470 QString AkonadiServer::serverPath() const
471 {
472  return StandardDirs::saveDir("config");
473 }
474 
475 #include "akonadi.moc"
Listens to agent instance added/removed signals and creates/removes the corresponding data in the dat...
QSqlError lastError() const const
Interval checking thread.
Definition: intervalcheck.h:38
SearchManager creates and deletes persistent searches for all currently active search engines...
Definition: searchmanager.h:49
Provides statistics information of a Collection.
std::optional< QSqlQuery > query(const QString &queryStatement)
Returns the cached (and prepared) query for queryStatement.
Definition: querycache.cpp:108
virtual bool init()
Initializes the database.
Definition: datastore.cpp:190
bool remove()
QObject * sender() const const
bool registerObject(const QString &path, QObject *object, QDBusConnection::RegisterOptions options)
bool isEmpty() const const
QString message() const const
QSqlDatabase addDatabase(const QString &type, const QString &connectionName)
QDBusConnection sessionBus()
virtual bool setPermissions(QFileDevice::Permissions permissions) override
The global tracer instance where all akonadi components can send their tracing information to...
Definition: tracer.h:53
bool isValid() const const
void setValue(const QString &key, const QVariant &value)
void exit(int returnCode)
The manager for preprocessor agents.
void removeDatabase(const QString &connectionName)
QString fromUtf8(const char *str, int size)
Manages and processes item retrieval requests.
QSqlDatabase database()
Returns the QSqlDatabase object.
Definition: datastore.cpp:154
void serviceUnregistered(const QString &serviceName)
bool isOpen() const const
KDEGAMES_EXPORT QAction * quit(const QObject *recvr, const char *slot, QObject *parent)
QVariant value(const QString &key, const QVariant &defaultValue) const const
Helper integration between Akonadi and Qt.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QByteArray toPercentEncoding(const QString &input, const QByteArray &exclude, const QByteArray &include)
int length() const const
QString text() const const
void setDatabaseName(const QString &name)
QDBusError lastError() const const
This class handles all the database access.
Definition: datastore.h:108
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString toString() const const
QDir home()
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Fri Jun 5 2020 23:08:53 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.