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
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:
AkonadiDataStore(AkonadiServer & server)53 explicit AkonadiDataStore(AkonadiServer &server)
54 : DataStore(server)
55 {
56 }
57 };
58
59 class AkonadiDataStoreFactory : public DataStoreFactory
60 {
61 public:
AkonadiDataStoreFactory(AkonadiServer & akonadi)62 explicit AkonadiDataStoreFactory(AkonadiServer &akonadi)
63 : m_akonadi(akonadi)
64 {
65 }
66
createStore()67 DataStore *createStore() override
68 {
69 return new AkonadiDataStore(m_akonadi);
70 }
71
72 private:
73 AkonadiServer &m_akonadi;
74 };
75
76 } // namespace
77
AkonadiServer()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
init()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
96 QFile::setPermissions(serverConfigFile, QFile::ReadOwner | QFile::WriteOwner);
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...
152 DataStore::self()->unhideAllPimItems();
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
quit()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
doQuit()212 void AkonadiServer::doQuit()
213 {
214 QCoreApplication::exit();
215 }
216
newCmdConnection(quintptr socketDescriptor)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
connectionDisconnected()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
setupDatabase()237 bool AkonadiServer::setupDatabase()
238 {
239 if (!DbConfig::configuredDatabase()) {
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
253 DbConfig::configuredDatabase()->setup();
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
startDatabaseProcess()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
279 return DbConfig::configuredDatabase()->startInternalServer();
280 }
281
createDatabase()282 bool AkonadiServer::createDatabase()
283 {
284 bool success = true;
285 const QLatin1String initCon("initConnection");
286 QSqlDatabase db = QSqlDatabase::addDatabase(DbConfig::configuredDatabase()->driverName(), initCon);
287 DbConfig::configuredDatabase()->apply(db);
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
stopDatabaseProcess()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
331 DbConfig::configuredDatabase()->stopInternalServer();
332 }
333
createServers(QSettings & settings,QSettings & connectionSettings)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
cacheCleaner()400 CacheCleaner *AkonadiServer::cacheCleaner()
401 {
402 return mCacheCleaner.get();
403 }
404
intervalChecker()405 IntervalCheck &AkonadiServer::intervalChecker()
406 {
407 return *mIntervalCheck;
408 }
409
resourceManager()410 ResourceManager &AkonadiServer::resourceManager()
411 {
412 return *mResourceManager;
413 }
414
notificationManager()415 NotificationManager *AkonadiServer::notificationManager()
416 {
417 return mNotificationManager.get();
418 }
419
collectionStatistics()420 CollectionStatistics &AkonadiServer::collectionStatistics()
421 {
422 return *mCollectionStats;
423 }
424
preprocessorManager()425 PreprocessorManager &AkonadiServer::preprocessorManager()
426 {
427 return *mPreprocessorManager;
428 }
429
agentSearchManager()430 SearchTaskManager &AkonadiServer::agentSearchManager()
431 {
432 return *mAgentSearchManager;
433 }
434
searchManager()435 SearchManager &AkonadiServer::searchManager()
436 {
437 return *mSearchManager;
438 }
439
itemRetrievalManager()440 ItemRetrievalManager &AkonadiServer::itemRetrievalManager()
441 {
442 return *mItemRetrieval;
443 }
444
tracer()445 Tracer &AkonadiServer::tracer()
446 {
447 return *mTracer;
448 }
449
serverPath() const450 QString AkonadiServer::serverPath() const
451 {
452 return StandardDirs::saveDir("config");
453 }
454
455 #include "akonadi.moc"
456