Akonadi

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

KDE's Doxygen guidelines are available online.