1/***************************************************************************
2 * Copyright (C) 2006 by Till Adam <adam@kde.org> *
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 "connectionthread.h"
22#include "serveradaptor.h"
23#include <akdbus.h>
24#include <akdebug.h>
25#include <akstandarddirs.h>
26
27#include "cachecleaner.h"
28#include "intervalcheck.h"
29#include "storagejanitor.h"
30#include "storage/dbconfig.h"
31#include "storage/datastore.h"
32#include "notificationmanager.h"
33#include "resourcemanager.h"
34#include "tracer.h"
35#include "utils.h"
36#include "debuginterface.h"
37#include "storage/itemretrievalthread.h"
38#include "preprocessormanager.h"
39#include "search/searchmanager.h"
40#include "search/searchtaskmanagerthread.h"
41#include "response.h"
42#include "collectionreferencemanager.h"
43
44#include "libs/xdgbasedirs_p.h"
45#include "libs/protocol_p.h"
46
47#include <QtSql/QSqlQuery>
48#include <QtSql/QSqlError>
49
50#include <QtCore/QCoreApplication>
51#include <QtCore/QDir>
52#include <QtCore/QSettings>
53#include <QtCore/QTimer>
54#include <QtDBus/QDBusServiceWatcher>
55
56#include <config-akonadi.h>
57#ifdef HAVE_UNISTD_H
58# include <unistd.h>
59#endif
60#include <stdlib.h>
61
62#ifdef Q_WS_WIN
63#include <windows.h>
64#include <Sddl.h>
65#endif
66
67using namespace Akonadi;
68using namespace Akonadi::Server;
69
70AkonadiServer *AkonadiServer::s_instance = 0;
71
72AkonadiServer::AkonadiServer(QObject *parent)
73 : QLocalServer(parent)
74 , mCacheCleaner(0)
75 , mIntervalChecker(0)
76 , mStorageJanitor(0)
77 , mItemRetrievalThread(0)
78 , mDatabaseProcess(0)
79 , mAlreadyShutdown(false)
80{
81}
82
83bool AkonadiServer::init()
84{
85 qRegisterMetaType<Akonadi::Server::Response>();
86
87 const QString serverConfigFile = AkStandardDirs::serverConfigFile(XdgBaseDirs::ReadWrite);
88 QSettings settings(serverConfigFile, QSettings::IniFormat);
89 // Restrict permission to 600, as the file might contain database password in plaintext
90 QFile::setPermissions(serverConfigFile, QFile::ReadOwner | QFile::WriteOwner);
91
92 if (DbConfig::configuredDatabase()->useInternalServer()) {
93 startDatabaseProcess();
94 } else {
95 createDatabase();
96 }
97
98 DbConfig::configuredDatabase()->setup();
99
100 s_instance = this;
101
102 const QString connectionSettingsFile = AkStandardDirs::connectionConfigFile(XdgBaseDirs::WriteOnly);
103 QSettings connectionSettings(connectionSettingsFile, QSettings::IniFormat);
104
105#ifdef Q_OS_WIN
106 HANDLE hToken = NULL;
107 PSID sid;
108 QString userID;
109
110 OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken);
111 if (hToken) {
112 DWORD size;
113 PTOKEN_USER userStruct;
114
115 GetTokenInformation(hToken, TokenUser, NULL, 0, &size);
116 if (ERROR_INSUFFICIENT_BUFFER == GetLastError()) {
117 userStruct = reinterpret_cast<PTOKEN_USER>(new BYTE[size]);
118 GetTokenInformation(hToken, TokenUser, userStruct, size, &size);
119
120 int sidLength = GetLengthSid(userStruct->User.Sid);
121 sid = (PSID) malloc(sidLength);
122 CopySid(sidLength, sid, userStruct->User.Sid);
123 CloseHandle(hToken);
124 delete [] userStruct;
125 }
126
127 LPWSTR s;
128 if (!ConvertSidToStringSidW(sid, &s)) {
129 akError() << "Could not determine user id for current process.";
130 userID = QString();
131 } else {
132 userID = QString::fromUtf16(reinterpret_cast<ushort *>(s));
133 LocalFree(s);
134 }
135 free(sid);
136 }
137 QString defaultPipe = QLatin1String("Akonadi-") + userID;
138
139 QString namedPipe = settings.value(QLatin1String("Connection/NamedPipe"), defaultPipe).toString();
140 if (!listen(namedPipe)) {
141 akFatal() << "Unable to listen on Named Pipe" << namedPipe;
142 }
143
144 connectionSettings.setValue(QLatin1String("Data/Method"), QLatin1String("NamedPipe"));
145 connectionSettings.setValue(QLatin1String("Data/NamedPipe"), namedPipe);
146#else
147 const QString socketDir = Utils::preferredSocketDirectory(AkStandardDirs::saveDir("data"));
148 const QString socketFile = socketDir + QLatin1String("/akonadiserver.socket");
149 unlink(socketFile.toUtf8().constData());
150 if (!listen(socketFile)) {
151 akFatal() << "Unable to listen on Unix socket" << socketFile;
152 }
153
154 connectionSettings.setValue(QLatin1String("Data/Method"), QLatin1String("UnixPath"));
155 connectionSettings.setValue(QLatin1String("Data/UnixPath"), socketFile);
156#endif
157
158 // initialize the database
159 DataStore *db = DataStore::self();
160 if (!db->database().isOpen()) {
161 akFatal() << "Unable to open database" << db->database().lastError().text();
162 }
163 if (!db->init()) {
164 akFatal() << "Unable to initialize database.";
165 }
166
167 NotificationManager::self();
168 Tracer::self();
169 new DebugInterface(this);
170 ResourceManager::self();
171
172 // Initialize the preprocessor manager
173 PreprocessorManager::init();
174
175 // Forcibly disable it if configuration says so
176 if (settings.value(QLatin1String("General/DisablePreprocessing"), false).toBool()) {
177 PreprocessorManager::instance()->setEnabled(false);
178 }
179
180 if (settings.value(QLatin1String("Cache/EnableCleaner"), true).toBool()) {
181 mCacheCleaner = new CacheCleaner(this);
182 mCacheCleaner->start(QThread::IdlePriority);
183 }
184
185 mIntervalChecker = new IntervalCheck(this);
186 mIntervalChecker->start(QThread::IdlePriority);
187
188 mStorageJanitor = new StorageJanitorThread;
189 mStorageJanitor->start(QThread::IdlePriority);
190
191 mItemRetrievalThread = new ItemRetrievalThread(this);
192 mItemRetrievalThread->start(QThread::HighPriority);
193
194 mAgentSearchManagerThread = new SearchTaskManagerThread(this);
195 mAgentSearchManagerThread->start();
196
197 const QStringList searchManagers = settings.value(QLatin1String("Search/Manager"),
198 QStringList() << QLatin1String("Nepomuk")
199 << QLatin1String("Agent")).toStringList();
200 mSearchManager = new SearchManagerThread(searchManagers, this);
201 mSearchManager->start();
202
203 new ServerAdaptor(this);
204 QDBusConnection::sessionBus().registerObject(QLatin1String("/Server"), this);
205
206 const QByteArray dbusAddress = qgetenv("DBUS_SESSION_BUS_ADDRESS");
207 if (!dbusAddress.isEmpty()) {
208 connectionSettings.setValue(QLatin1String("DBUS/Address"), QLatin1String(dbusAddress));
209 }
210
211 QDBusServiceWatcher *watcher = new QDBusServiceWatcher(AkDBus::serviceName(AkDBus::Control),
212 QDBusConnection::sessionBus(),
213 QDBusServiceWatcher::WatchForOwnerChange, this);
214
215 connect(watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)),
216 this, SLOT(serviceOwnerChanged(QString,QString,QString)));
217
218 // Unhide all the items that are actually hidden.
219 // The hidden flag was probably left out after an (abrupt)
220 // server quit. We don't attempt to resume preprocessing
221 // for the items as we don't actually know at which stage the
222 // operation was interrupted...
223 db->unhideAllPimItems();
224
225 // Cleanup referenced collections from the last run
226 CollectionReferenceManager::cleanup();
227
228 // We are ready, now register org.freedesktop.Akonadi service to DBus and
229 // the fun can begin
230 if (!QDBusConnection::sessionBus().registerService(AkDBus::serviceName(AkDBus::Server))) {
231 akFatal() << "Unable to connect to dbus service: " << QDBusConnection::sessionBus().lastError().message();
232 }
233
234 return true;
235}
236
237AkonadiServer::~AkonadiServer()
238{
239}
240
241template <typename T> static void quitThread(T &thread)
242{
243 if (!thread) {
244 return;
245 }
246 thread->quit();
247 thread->wait();
248 delete thread;
249 thread = 0;
250}
251
252bool AkonadiServer::quit()
253{
254 if (mAlreadyShutdown) {
255 return true;
256 }
257 mAlreadyShutdown = true;
258
259 akDebug() << "terminating service threads";
260 quitThread(mCacheCleaner);
261 quitThread(mIntervalChecker);
262 quitThread(mStorageJanitor);
263 quitThread(mItemRetrievalThread);
264 mAgentSearchManagerThread->stop();
265 quitThread(mAgentSearchManagerThread);
266 quitThread(mSearchManager);
267
268 delete mSearchManager;
269 mSearchManager = 0;
270
271 akDebug() << "terminating connection threads";
272 for (int i = 0; i < mConnections.count(); ++i) {
273 quitThread(mConnections[i]);
274 }
275 mConnections.clear();
276
277 // Terminate the preprocessor manager before the database but after all connections are gone
278 PreprocessorManager::done();
279
280 DataStore::self()->close();
281
282 akDebug() << "stopping db process";
283 stopDatabaseProcess();
284
285 QSettings settings(AkStandardDirs::serverConfigFile(), QSettings::IniFormat);
286 const QString connectionSettingsFile = AkStandardDirs::connectionConfigFile(XdgBaseDirs::WriteOnly);
287
288#ifndef Q_OS_WIN
289 const QString socketDir = Utils::preferredSocketDirectory(AkStandardDirs::saveDir("data"));
290
291 if (!QDir::home().remove(socketDir + QLatin1String("/akonadiserver.socket"))) {
292 akError() << "Failed to remove Unix socket";
293 }
294#endif
295 if (!QDir::home().remove(connectionSettingsFile)) {
296 akError() << "Failed to remove runtime connection config file";
297 }
298
299 QTimer::singleShot(0, this, SLOT(doQuit()));
300
301 return true;
302}
303
304void AkonadiServer::doQuit()
305{
306 QCoreApplication::exit();
307}
308
309void AkonadiServer::incomingConnection(quintptr socketDescriptor)
310{
311 if (mAlreadyShutdown) {
312 return;
313 }
314 QPointer<ConnectionThread> thread = new ConnectionThread(socketDescriptor, this);
315 connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
316 mConnections.append(thread);
317 thread->start();
318}
319
320AkonadiServer *AkonadiServer::instance()
321{
322 if (!s_instance) {
323 s_instance = new AkonadiServer();
324 }
325 return s_instance;
326}
327
328void AkonadiServer::startDatabaseProcess()
329{
330 if (!DbConfig::configuredDatabase()->useInternalServer()) {
331 return;
332 }
333
334 // create the database directories if they don't exists
335 AkStandardDirs::saveDir("data");
336 AkStandardDirs::saveDir("data", QLatin1String("file_db_data"));
337
338 DbConfig::configuredDatabase()->startInternalServer();
339}
340
341void AkonadiServer::createDatabase()
342{
343 const QLatin1String initCon("initConnection");
344 QSqlDatabase db = QSqlDatabase::addDatabase(DbConfig::configuredDatabase()->driverName(), initCon);
345 DbConfig::configuredDatabase()->apply(db);
346 db.setDatabaseName(DbConfig::configuredDatabase()->databaseName());
347 if (!db.isValid()) {
348 akFatal() << "Invalid database object during initial database connection";
349 }
350
351 if (db.open()) {
352 db.close();
353 } else {
354 akDebug() << "Failed to use database" << DbConfig::configuredDatabase()->databaseName();
355 akDebug() << "Database error:" << db.lastError().text();
356 akDebug() << "Trying to create database now...";
357
358 db.close();
359 db.setDatabaseName(QString());
360 if (db.open()) {
361 {
362 QSqlQuery query(db);
363 if (!query.exec(QString::fromLatin1("CREATE DATABASE %1").arg(DbConfig::configuredDatabase()->databaseName()))) {
364 akError() << "Failed to create database";
365 akError() << "Query error:" << query.lastError().text();
366 akFatal() << "Database error:" << db.lastError().text();
367 }
368 } // make sure query is destroyed before we close the db
369 db.close();
370 }
371 }
372 QSqlDatabase::removeDatabase(initCon);
373}
374
375void AkonadiServer::stopDatabaseProcess()
376{
377 if (!DbConfig::configuredDatabase()->useInternalServer()) {
378 return;
379 }
380
381 DbConfig::configuredDatabase()->stopInternalServer();
382}
383
384void AkonadiServer::serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner)
385{
386 Q_UNUSED(name);
387 Q_UNUSED(oldOwner);
388 if (newOwner.isEmpty()) {
389 akError() << "Control process died, committing suicide!";
390 quit();
391 }
392}
393
394CacheCleaner *AkonadiServer::cacheCleaner()
395{
396 return mCacheCleaner;
397}
398
399IntervalCheck *AkonadiServer::intervalChecker()
400{
401 return mIntervalChecker;
402}
403