Akonadi

akonadi.cpp
1/***************************************************************************
2 * SPDX-FileCopyrightText: 2006 Till Adam <adam@kde.org> *
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
44using namespace Akonadi;
45using namespace Akonadi::Server;
46using namespace std::chrono_literals;
47namespace
48{
49class AkonadiDataStore : public DataStore
50{
51 Q_OBJECT
52public:
53 explicit AkonadiDataStore(AkonadiServer *server)
54 : DataStore(server, DbConfig::configuredDatabase())
55 {
56 }
57};
58
59class AkonadiDataStoreFactory : public DataStoreFactory
60{
61public:
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
72private:
73 AkonadiServer *const m_akonadi;
74};
75
76} // namespace
77
78AkonadiServer::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
89bool 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"), QLatin1StringView(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 = AkThread::create<CacheCleaner>();
123 mItemRetrieval = AkThread::create<ItemRetrievalManager>();
124 mAgentSearchManager = AkThread::create<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 = AkThread::create<IntervalCheck>(*mItemRetrieval);
130 mSearchManager = AkThread::create<SearchManager>(searchManagers, *mAgentSearchManager);
131 mStorageJanitor = AkThread::create<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, exiting!";
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
165AkonadiServer::~AkonadiServer() = default;
166
167bool 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
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
212void AkonadiServer::doQuit()
213{
215}
216
217void AkonadiServer::newCmdConnection(quintptr socketDescriptor)
218{
219 if (mAlreadyShutdown) {
220 return;
221 }
222
223 auto connection = AkThread::create<Connection>(socketDescriptor, *this);
224 connect(connection.get(), &Connection::disconnected, this, &AkonadiServer::connectionDisconnected);
225 mConnections.push_back(std::move(connection));
226}
227
228void AkonadiServer::connectionDisconnected()
229{
230 auto it = std::find_if(mConnections.begin(), mConnections.end(), [this](const auto &ptr) {
231 return ptr.get() == sender();
232 });
233
234 if (it != mConnections.end()) {
235 mConnections.erase(it);
236 }
237}
238
239bool AkonadiServer::setupDatabase()
240{
242 return false;
243 }
244
245 if (DbConfig::configuredDatabase()->useInternalServer()) {
246 if (!startDatabaseProcess()) {
247 return false;
248 }
249 } else {
250 if (!createDatabase()) {
251 return false;
252 }
253 }
254
256
257 // initialize the database
259 if (!db->database().isOpen()) {
260 qCCritical(AKONADISERVER_LOG) << "Unable to open database" << db->database().lastError().text();
261 return false;
262 }
263 if (!db->init()) {
264 qCCritical(AKONADISERVER_LOG) << "Unable to initialize database.";
265 return false;
266 }
267
268 return true;
269}
270
271bool AkonadiServer::startDatabaseProcess()
272{
273 if (!DbConfig::configuredDatabase()->useInternalServer()) {
274 qCCritical(AKONADISERVER_LOG) << "Trying to start external database!";
275 }
276
277 // create the database directories if they don't exists
278 StandardDirs::saveDir("data");
279 StandardDirs::saveDir("data", QStringLiteral("file_db_data"));
280
282}
283
284bool AkonadiServer::createDatabase()
285{
286 bool success = true;
287 const QLatin1StringView initCon("initConnection");
290 db.setDatabaseName(DbConfig::configuredDatabase()->databaseName());
291 if (!db.isValid()) {
292 qCCritical(AKONADISERVER_LOG) << "Invalid database object during initial database connection";
293 return false;
294 }
295
296 if (db.open()) {
297 db.close();
298 } else {
299 qCCritical(AKONADISERVER_LOG) << "Failed to use database" << DbConfig::configuredDatabase()->databaseName();
300 qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
301 qCDebug(AKONADISERVER_LOG) << "Trying to create database now...";
302
303 db.close();
305 if (db.open()) {
306 {
307 QSqlQuery query(db);
308 if (!query.exec(QStringLiteral("CREATE DATABASE %1").arg(DbConfig::configuredDatabase()->databaseName()))) {
309 qCCritical(AKONADISERVER_LOG) << "Failed to create database";
310 qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text();
311 qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
312 success = false;
313 }
314 } // make sure query is destroyed before we close the db
315 db.close();
316 } else {
317 qCCritical(AKONADISERVER_LOG) << "Failed to connect to database!";
318 qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text();
319 success = false;
320 }
321 }
322 return success;
323}
324
325void AkonadiServer::stopDatabaseProcess()
326{
327 if (!DbConfig::configuredDatabase()->useInternalServer()) {
328 // closing initConnection this late to work around QTBUG-63108
329 QSqlDatabase::removeDatabase(QStringLiteral("initConnection"));
330 return;
331 }
332
334}
335
336bool AkonadiServer::createServers(QSettings &settings, QSettings &connectionSettings)
337{
338 mCmdServer = std::make_unique<AkLocalServer>(this);
339 connect(mCmdServer.get(), qOverload<quintptr>(&AkLocalServer::newConnection), this, &AkonadiServer::newCmdConnection);
340
341 mNotificationManager = AkThread::create<NotificationManager>();
342 mNtfServer = std::make_unique<AkLocalServer>(this);
343 // Note: this is a queued connection, as NotificationManager lives in its
344 // own thread
345 connect(mNtfServer.get(), qOverload<quintptr>(&AkLocalServer::newConnection), mNotificationManager.get(), &NotificationManager::registerConnection);
346
347 // TODO: share socket setup with client
348#ifdef Q_OS_WIN
349 // use the installation prefix as uid
350 QString suffix;
351 if (Instance::hasIdentifier()) {
352 suffix = QStringLiteral("%1-").arg(Instance::identifier());
353 }
354 suffix += QString::fromUtf8(QUrl::toPercentEncoding(qApp->applicationDirPath()));
355 const QString defaultCmdPipe = QStringLiteral("Akonadi-Cmd-") % suffix;
356 const QString cmdPipe = settings.value(QStringLiteral("Connection/NamedPipe"), defaultCmdPipe).toString();
357 if (!mCmdServer->listen(cmdPipe)) {
358 qCCritical(AKONADISERVER_LOG) << "Unable to listen on Named Pipe" << cmdPipe << ":" << mCmdServer->errorString();
359 return false;
360 }
361
362 const QString defaultNtfPipe = QStringLiteral("Akonadi-Ntf-") % suffix;
363 const QString ntfPipe = settings.value(QStringLiteral("Connection/NtfNamedPipe"), defaultNtfPipe).toString();
364 if (!mNtfServer->listen(ntfPipe)) {
365 qCCritical(AKONADISERVER_LOG) << "Unable to listen on Named Pipe" << ntfPipe << ":" << mNtfServer->errorString();
366 return false;
367 }
368
369 connectionSettings.setValue(QStringLiteral("Data/Method"), QStringLiteral("NamedPipe"));
370 connectionSettings.setValue(QStringLiteral("Data/NamedPipe"), cmdPipe);
371 connectionSettings.setValue(QStringLiteral("Notifications/Method"), QStringLiteral("NamedPipe"));
372 connectionSettings.setValue(QStringLiteral("Notifications/NamedPipe"), ntfPipe);
373#else
374 Q_UNUSED(settings)
375
376 const QString cmdSocketName = QStringLiteral("akonadiserver-cmd.socket");
377 const QString ntfSocketName = QStringLiteral("akonadiserver-ntf.socket");
378 const QString socketDir = Utils::preferredSocketDirectory(StandardDirs::saveDir("data"), qMax(cmdSocketName.length(), ntfSocketName.length()));
379 const QString cmdSocketFile = socketDir % QLatin1Char('/') % cmdSocketName;
380 QFile::remove(cmdSocketFile);
381 if (!mCmdServer->listen(cmdSocketFile)) {
382 qCCritical(AKONADISERVER_LOG) << "Unable to listen on Unix socket" << cmdSocketFile << ":" << mCmdServer->errorString();
383 return false;
384 }
385
386 const QString ntfSocketFile = socketDir % QLatin1Char('/') % ntfSocketName;
387 QFile::remove(ntfSocketFile);
388 if (!mNtfServer->listen(ntfSocketFile)) {
389 qCCritical(AKONADISERVER_LOG) << "Unable to listen on Unix socket" << ntfSocketFile << ":" << mNtfServer->errorString();
390 return false;
391 }
392
393 connectionSettings.setValue(QStringLiteral("Data/Method"), QStringLiteral("UnixPath"));
394 connectionSettings.setValue(QStringLiteral("Data/UnixPath"), cmdSocketFile);
395 connectionSettings.setValue(QStringLiteral("Notifications/Method"), QStringLiteral("UnixPath"));
396 connectionSettings.setValue(QStringLiteral("Notifications/UnixPath"), ntfSocketFile);
397#endif
398
399 return true;
400}
401
402CacheCleaner *AkonadiServer::cacheCleaner()
403{
404 return mCacheCleaner.get();
405}
406
407IntervalCheck &AkonadiServer::intervalChecker()
408{
409 return *mIntervalCheck;
410}
411
412ResourceManager &AkonadiServer::resourceManager()
413{
414 return *mResourceManager;
415}
416
417NotificationManager *AkonadiServer::notificationManager()
418{
419 return mNotificationManager.get();
420}
421
422CollectionStatistics &AkonadiServer::collectionStatistics()
423{
424 return *mCollectionStats;
425}
426
427PreprocessorManager &AkonadiServer::preprocessorManager()
428{
429 return *mPreprocessorManager;
430}
431
432SearchTaskManager &AkonadiServer::agentSearchManager()
433{
434 return *mAgentSearchManager;
435}
436
437SearchManager &AkonadiServer::searchManager()
438{
439 return *mSearchManager;
440}
441
442ItemRetrievalManager &AkonadiServer::itemRetrievalManager()
443{
444 return *mItemRetrieval;
445}
446
447Tracer &AkonadiServer::tracer()
448{
449 return *mTracer;
450}
451
452QString AkonadiServer::serverPath() const
453{
454 return StandardDirs::saveDir("config");
455}
456
457#include "akonadi.moc"
458
459#include "moc_akonadi.cpp"
Provides statistics information of a Collection.
This class handles all the database access.
Definition datastore.h:95
void close()
Closes the database connection.
static DataStore * self()
Per thread singleton.
virtual bool unhideAllPimItems()
Unhides all the items which have the "hidden" flag set.
virtual bool init()
Initializes the database.
static bool hasDataStore()
Returns whether per thread DataStore has been created.
QSqlDatabase database()
Returns the QSqlDatabase object.
A base class that provides an unique access layer to configuration and initialization of different da...
Definition dbconfig.h:21
virtual void setup()
This method is called to setup initial database settings after a connection is established.
Definition dbconfig.cpp:133
virtual void apply(QSqlDatabase &database)=0
This method applies the configured settings to the QtSql database instance.
static bool isConfigured()
Returns whether database have been configured.
Definition dbconfig.cpp:53
virtual QString databaseName() const =0
Returns the database name.
virtual void stopInternalServer()
This method is called to stop the external server.
Definition dbconfig.cpp:128
static DbConfig * configuredDatabase()
Returns the DbConfig instance for the database the user has configured.
Definition dbconfig.cpp:77
virtual bool startInternalServer()
This method is called to start an external server.
Definition dbconfig.cpp:122
Interval checking thread.
Manages and processes item retrieval requests.
The manager for preprocessor agents.
Listens to agent instance added/removed signals and creates/removes the corresponding data in the dat...
SearchManager creates and deletes persistent searches for all currently active search engines.
The global tracer instance where all akonadi components can send their tracing information to.
Definition tracer.h:38
Helper integration between Akonadi and Qt.
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
KGuiItem remove()
bool isEmpty() const const
void exit(int returnCode)
QDBusError lastError() const const
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
QDBusConnection sessionBus()
QString message() const const
void serviceUnregistered(const QString &serviceName)
QDir home()
bool remove()
virtual bool setPermissions(Permissions permissions) override
void newConnection()
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void setValue(QAnyStringView key, const QVariant &value)
QVariant value(QAnyStringView key) const const
QSqlDatabase addDatabase(QSqlDriver *driver, const QString &connectionName)
bool isOpen() const const
bool isValid() const const
QSqlError lastError() const const
void removeDatabase(const QString &connectionName)
void setDatabaseName(const QString &name)
QString text() const const
QString arg(Args &&... args) const const
QString fromUtf8(QByteArrayView str)
qsizetype length() const const
QByteArray toPercentEncoding(const QString &input, const QByteArray &exclude, const QByteArray &include)
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 6 2024 12:00:59 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.