diff --git a/src/server/akonadi.cpp b/src/server/akonadi.cpp index 0a4843e12..9d5f30437 100644 --- a/src/server/akonadi.cpp +++ b/src/server/akonadi.cpp @@ -1,470 +1,472 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "akonadi.h" #include "handler.h" #include "connection.h" #include "serveradaptor.h" #include "akonadiserver_debug.h" #include "cachecleaner.h" #include "intervalcheck.h" #include "storagejanitor.h" #include "storage/dbconfig.h" #include "storage/datastore.h" #include "notificationmanager.h" #include "resourcemanager.h" #include "tracer.h" #include "utils.h" #include "debuginterface.h" #include "storage/itemretrievalmanager.h" #include "storage/collectionstatistics.h" #include "preprocessormanager.h" #include "search/searchmanager.h" #include "search/searchtaskmanager.h" #include "aklocalserver.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; namespace { class AkonadiDataStore : public DataStore { public: AkonadiDataStore(AkonadiServer &server): DataStore(server) {} }; class AkonadiDataStoreFactory : public DataStoreFactory { public: AkonadiDataStoreFactory(AkonadiServer &akonadi) : DataStoreFactory() , m_akonadi(akonadi) {} DataStore *createStore() override { return new AkonadiDataStore(m_akonadi); } private: AkonadiServer &m_akonadi; }; } AkonadiServer::AkonadiServer() : QObject() { // Register bunch of useful types qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType("quintptr"); DataStore::setFactory(std::make_unique(*this)); } bool AkonadiServer::init() { qCInfo(AKONADISERVER_LOG) << "Starting up the Akonadi Server..."; const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite); QSettings settings(serverConfigFile, QSettings::IniFormat); // Restrict permission to 600, as the file might contain database password in plaintext QFile::setPermissions(serverConfigFile, QFile::ReadOwner | QFile::WriteOwner); const QString connectionSettingsFile = StandardDirs::connectionConfigFile(StandardDirs::WriteOnly); QSettings connectionSettings(connectionSettingsFile, QSettings::IniFormat); const QByteArray dbusAddress = qgetenv("DBUS_SESSION_BUS_ADDRESS"); if (!dbusAddress.isEmpty()) { connectionSettings.setValue(QStringLiteral("DBUS/Address"), QLatin1String(dbusAddress)); } // Setup database if (!setupDatabase()) { quit(); return false; } // Create local servers and start listening - if (!createServers(connectionSettings)) { + if (!createServers(settings, connectionSettings)) { quit(); return false; } const auto searchManagers = settings.value(QStringLiteral("Search/Manager"), QStringList{QStringLiteral("Agent")}).toStringList(); mTracer = std::make_unique(); mCollectionStats = std::make_unique(); mCacheCleaner = std::make_unique(); mItemRetrieval = std::make_unique(); mAgentSearchManager = std::make_unique(); mDebugInterface = std::make_unique(*mTracer.get()); mResourceManager = std::make_unique(*mTracer.get()); mPreprocessorManager = std::make_unique(*mTracer.get()); mIntervalCheck = std::make_unique(*mItemRetrieval.get()); mSearchManager = std::make_unique(searchManagers, *mAgentSearchManager.get()); mStorageJanitor = std::make_unique(*this); if (settings.value(QStringLiteral("General/DisablePreprocessing"), false).toBool()) { mPreprocessorManager->setEnabled(false); } new ServerAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/Server"), this); mControlWatcher = std::make_unique( DBus::serviceName(DBus::Control), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForUnregistration); connect(mControlWatcher.get(), &QDBusServiceWatcher::serviceUnregistered, this, [this]() { qCCritical(AKONADISERVER_LOG) << "Control process died, committing suicide!"; quit(); }); // Unhide all the items that are actually hidden. // The hidden flag was probably left out after an (abrupt) // server quit. We don't attempt to resume preprocessing // for the items as we don't actually know at which stage the // operation was interrupted... DataStore::self()->unhideAllPimItems(); // We are ready, now register org.freedesktop.Akonadi service to DBus and // the fun can begin if (!QDBusConnection::sessionBus().registerService(DBus::serviceName(DBus::Server))) { qCCritical(AKONADISERVER_LOG) << "Unable to connect to dbus service: " << QDBusConnection::sessionBus().lastError().message(); quit(); return false; } return true; } AkonadiServer::~AkonadiServer() = default; bool AkonadiServer::quit() { if (mAlreadyShutdown) { return true; } mAlreadyShutdown = true; qCDebug(AKONADISERVER_LOG) << "terminating connection threads"; mConnections.clear(); qCDebug(AKONADISERVER_LOG) << "terminating service threads"; // Keep this order in sync (reversed) with the order of initialization mStorageJanitor.reset(); mSearchManager.reset(); mIntervalCheck.reset(); mPreprocessorManager.reset(); mResourceManager.reset(); mDebugInterface.reset(); mAgentSearchManager.reset(); mItemRetrieval.reset(); mCacheCleaner.reset(); mCollectionStats.reset(); mTracer.reset(); if (DbConfig::isConfigured()) { if (DataStore::hasDataStore()) { DataStore::self()->close(); } qCDebug(AKONADISERVER_LOG) << "stopping db process"; stopDatabaseProcess(); } //QSettings settings(StandardDirs::serverConfigFile(), QSettings::IniFormat); const QString connectionSettingsFile = StandardDirs::connectionConfigFile(StandardDirs::WriteOnly); if (!QDir::home().remove(connectionSettingsFile)) { qCCritical(AKONADISERVER_LOG) << "Failed to remove runtime connection config file"; } QTimer::singleShot(0, this, &AkonadiServer::doQuit); return true; } void AkonadiServer::doQuit() { QCoreApplication::exit(); } void AkonadiServer::newCmdConnection(quintptr socketDescriptor) { if (mAlreadyShutdown) { return; } auto connection = std::make_unique(socketDescriptor, *this); connect(connection.get(), &Connection::disconnected, this, &AkonadiServer::connectionDisconnected); mConnections.push_back(std::move(connection)); } void AkonadiServer::connectionDisconnected() { auto it = std::find_if(mConnections.begin(), mConnections.end(), [this](const auto &ptr) { return ptr.get() == sender(); }); Q_ASSERT(it != mConnections.end()); mConnections.erase(it); } bool AkonadiServer::setupDatabase() { if (!DbConfig::configuredDatabase()) { return false; } if (DbConfig::configuredDatabase()->useInternalServer()) { if (!startDatabaseProcess()) { return false; } } else { if (!createDatabase()) { return false; } } DbConfig::configuredDatabase()->setup(); // initialize the database DataStore *db = DataStore::self(); if (!db->database().isOpen()) { qCCritical(AKONADISERVER_LOG) << "Unable to open database" << db->database().lastError().text(); return false; } if (!db->init()) { qCCritical(AKONADISERVER_LOG) << "Unable to initialize database."; return false; } return true; } bool AkonadiServer::startDatabaseProcess() { if (!DbConfig::configuredDatabase()->useInternalServer()) { qCCritical(AKONADISERVER_LOG) << "Trying to start external database!"; } // create the database directories if they don't exists StandardDirs::saveDir("data"); StandardDirs::saveDir("data", QStringLiteral("file_db_data")); return DbConfig::configuredDatabase()->startInternalServer(); } bool AkonadiServer::createDatabase() { bool success = true; const QLatin1String initCon("initConnection"); QSqlDatabase db = QSqlDatabase::addDatabase(DbConfig::configuredDatabase()->driverName(), initCon); DbConfig::configuredDatabase()->apply(db); db.setDatabaseName(DbConfig::configuredDatabase()->databaseName()); if (!db.isValid()) { qCCritical(AKONADISERVER_LOG) << "Invalid database object during initial database connection"; return false; } if (db.open()) { db.close(); } else { qCCritical(AKONADISERVER_LOG) << "Failed to use database" << DbConfig::configuredDatabase()->databaseName(); qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); qCDebug(AKONADISERVER_LOG) << "Trying to create database now..."; db.close(); db.setDatabaseName(QString()); if (db.open()) { { QSqlQuery query(db); if (!query.exec(QStringLiteral("CREATE DATABASE %1").arg(DbConfig::configuredDatabase()->databaseName()))) { qCCritical(AKONADISERVER_LOG) << "Failed to create database"; qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text(); qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); success = false; } } // make sure query is destroyed before we close the db db.close(); } else { qCCritical(AKONADISERVER_LOG) << "Failed to connect to database!"; qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); success = false; } } return success; } void AkonadiServer::stopDatabaseProcess() { if (!DbConfig::configuredDatabase()->useInternalServer()) { // closing initConnection this late to work around QTBUG-63108 QSqlDatabase::removeDatabase(QStringLiteral("initConnection")); return; } DbConfig::configuredDatabase()->stopInternalServer(); } -bool AkonadiServer::createServers(QSettings &connectionSettings) +bool AkonadiServer::createServers(QSettings &settings, QSettings &connectionSettings) { mCmdServer = std::make_unique(this); connect(mCmdServer.get(), QOverload::of(&AkLocalServer::newConnection), this, &AkonadiServer::newCmdConnection); mNotificationManager = std::make_unique(); mNtfServer = std::make_unique(this); // Note: this is a queued connection, as NotificationManager lives in its // own thread connect(mNtfServer.get(), QOverload::of(&AkLocalServer::newConnection), mNotificationManager.get(), &NotificationManager::registerConnection); // TODO: share socket setup with client #ifdef Q_OS_WIN // use the installation prefix as uid QString suffix; if (Instance::hasIdentifier()) { suffix = QStringLiteral("%1-").arg(Instance::identifier()); } suffix += QString::fromUtf8(QUrl::toPercentEncoding(qApp->applicationDirPath())); const QString defaultCmdPipe = QStringLiteral("Akonadi-Cmd-") % suffix; const QString cmdPipe = settings.value(QStringLiteral("Connection/NamedPipe"), defaultCmdPipe).toString(); if (!mCmdServer->listen(cmdPipe)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Named Pipe" << cmdPipe << ":" << mCmdServer->errorString(); return false; } const QString defaultNtfPipe = QStringLiteral("Akonadi-Ntf-") % suffix; const QString ntfPipe = settings.value(QStringLiteral("Connection/NtfNamedPipe"), defaultNtfPipe).toString(); if (!mNtfServer->listen(ntfPipe)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Named Pipe" << ntfPipe << ":" << mNtfServer->errorString(); return false; } connectionSettings.setValue(QStringLiteral("Data/Method"), QStringLiteral("NamedPipe")); connectionSettings.setValue(QStringLiteral("Data/NamedPipe"), cmdPipe); connectionSettings.setValue(QStringLiteral("Notifications/Method"), QStringLiteral("NamedPipe")); connectionSettings.setValue(QStringLiteral("Notifications/NamedPipe"), ntfPipe); #else + Q_UNUSED(settings); + const QString cmdSocketName = QStringLiteral("akonadiserver-cmd.socket"); const QString ntfSocketName = QStringLiteral("akonadiserver-ntf.socket"); const QString socketDir = Utils::preferredSocketDirectory(StandardDirs::saveDir("data"), qMax(cmdSocketName.length(), ntfSocketName.length())); const QString cmdSocketFile = socketDir % QLatin1Char('/') % cmdSocketName; QFile::remove(cmdSocketFile); if (!mCmdServer->listen(cmdSocketFile)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Unix socket" << cmdSocketFile << ":" << mCmdServer->errorString(); return false; } const QString ntfSocketFile = socketDir % QLatin1Char('/') % ntfSocketName; QFile::remove(ntfSocketFile); if (!mNtfServer->listen(ntfSocketFile)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Unix socket" << ntfSocketFile << ":" << mNtfServer->errorString(); return false; } connectionSettings.setValue(QStringLiteral("Data/Method"), QStringLiteral("UnixPath")); connectionSettings.setValue(QStringLiteral("Data/UnixPath"), cmdSocketFile); connectionSettings.setValue(QStringLiteral("Notifications/Method"), QStringLiteral("UnixPath")); connectionSettings.setValue(QStringLiteral("Notifications/UnixPath"), ntfSocketFile); #endif return true; } CacheCleaner *AkonadiServer::cacheCleaner() { return mCacheCleaner.get(); } IntervalCheck &AkonadiServer::intervalChecker() { return *mIntervalCheck.get(); } ResourceManager &AkonadiServer::resourceManager() { return *mResourceManager.get(); } NotificationManager *AkonadiServer::notificationManager() { return mNotificationManager.get(); } CollectionStatistics &AkonadiServer::collectionStatistics() { return *mCollectionStats.get(); } PreprocessorManager &AkonadiServer::preprocessorManager() { return *mPreprocessorManager.get(); } SearchTaskManager &AkonadiServer::agentSearchManager() { return *mAgentSearchManager.get(); } SearchManager &AkonadiServer::searchManager() { return *mSearchManager.get(); } ItemRetrievalManager &AkonadiServer::itemRetrievalManager() { return *mItemRetrieval.get(); } Tracer &AkonadiServer::tracer() { return *mTracer.get(); } QString AkonadiServer::serverPath() const { return StandardDirs::saveDir("config"); } diff --git a/src/server/akonadi.h b/src/server/akonadi.h index 581dc7309..8df76d8cf 100644 --- a/src/server/akonadi.h +++ b/src/server/akonadi.h @@ -1,144 +1,144 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADISERVER_H #define AKONADISERVER_H #include #include #include #include class QProcess; class QDBusServiceWatcher; class QSettings; namespace Akonadi { namespace Server { class Connection; class ItemRetrievalManager; class SearchTaskManager; class SearchManager; class StorageJanitor; class CacheCleaner; class IntervalCheck; class AkLocalServer; class NotificationManager; class ResourceManager; class CollectionStatistics; class PreprocessorManager; class Tracer; class DebugInterface; class AkonadiServer : public QObject { Q_OBJECT public: explicit AkonadiServer(); ~AkonadiServer(); /** * Can return a nullptr */ CacheCleaner *cacheCleaner(); /** * Returns the IntervalCheck instance. Never nullptr. */ IntervalCheck &intervalChecker(); ResourceManager &resourceManager(); CollectionStatistics &collectionStatistics(); PreprocessorManager &preprocessorManager(); SearchTaskManager &agentSearchManager(); SearchManager &searchManager(); ItemRetrievalManager &itemRetrievalManager(); Tracer &tracer(); /** * Instance-aware server .config directory */ QString serverPath() const; /** * Can return a nullptr */ NotificationManager *notificationManager(); public Q_SLOTS: /** * Triggers a clean server shutdown. */ virtual bool quit(); virtual bool init(); protected Q_SLOTS: virtual void newCmdConnection(quintptr socketDescriptor); private Q_SLOTS: void doQuit(); void connectionDisconnected(); private: bool startDatabaseProcess(); bool createDatabase(); void stopDatabaseProcess(); - bool createServers(QSettings &connectionSettings); + bool createServers(QSettings &settings, QSettings &connectionSettings); bool setupDatabase(); uint userId() const; protected: std::unique_ptr mControlWatcher; std::unique_ptr mCmdServer; std::unique_ptr mNtfServer; std::unique_ptr mResourceManager; std::unique_ptr mDebugInterface; std::unique_ptr mCollectionStats; std::unique_ptr mPreprocessorManager; std::unique_ptr mNotificationManager; std::unique_ptr mCacheCleaner; std::unique_ptr mIntervalCheck; std::unique_ptr mStorageJanitor; std::unique_ptr mItemRetrieval; std::unique_ptr mAgentSearchManager; std::unique_ptr mSearchManager; std::unique_ptr mTracer; std::vector> mConnections; bool mAlreadyShutdown = false; }; } // namespace Server } // namespace Akonadi #endif