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 | |
67 | using namespace Akonadi; |
68 | using namespace Akonadi::Server; |
69 | |
70 | AkonadiServer *AkonadiServer::s_instance = 0; |
71 | |
72 | AkonadiServer::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 | |
83 | bool 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 | |
237 | AkonadiServer::~AkonadiServer() |
238 | { |
239 | } |
240 | |
241 | template <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 | |
252 | bool 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 | |
304 | void AkonadiServer::doQuit() |
305 | { |
306 | QCoreApplication::exit(); |
307 | } |
308 | |
309 | void 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 | |
320 | AkonadiServer *AkonadiServer::instance() |
321 | { |
322 | if (!s_instance) { |
323 | s_instance = new AkonadiServer(); |
324 | } |
325 | return s_instance; |
326 | } |
327 | |
328 | void 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 | |
341 | void 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 | |
375 | void AkonadiServer::stopDatabaseProcess() |
376 | { |
377 | if (!DbConfig::configuredDatabase()->useInternalServer()) { |
378 | return; |
379 | } |
380 | |
381 | DbConfig::configuredDatabase()->stopInternalServer(); |
382 | } |
383 | |
384 | void 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 | |
394 | CacheCleaner *AkonadiServer::cacheCleaner() |
395 | { |
396 | return mCacheCleaner; |
397 | } |
398 | |
399 | IntervalCheck *AkonadiServer::intervalChecker() |
400 | { |
401 | return mIntervalChecker; |
402 | } |
403 | |