File indexing completed on 2024-11-10 04:40:45
0001 /* 0002 SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "servermanager.h" 0008 #include "servermanager_p.h" 0009 0010 #include "agentmanager.h" 0011 #include "agenttype.h" 0012 #include "firstrun_p.h" 0013 #include "session_p.h" 0014 0015 #include "akonadicore_debug.h" 0016 0017 #include <KLocalizedString> 0018 0019 #include "private/dbus_p.h" 0020 #include "private/instance_p.h" 0021 #include "private/protocol_p.h" 0022 #include "private/standarddirs_p.h" 0023 0024 #include <QDBusConnection> 0025 #include <QDBusConnectionInterface> 0026 #include <QDBusInterface> 0027 #include <QDBusReply> 0028 #include <QDBusServiceWatcher> 0029 #include <QProcess> 0030 #include <QScopedPointer> 0031 #include <QStandardPaths> 0032 #include <QTimer> 0033 #include <qnamespace.h> 0034 0035 using namespace Akonadi; 0036 0037 class Akonadi::ServerManagerPrivate 0038 { 0039 public: 0040 ServerManagerPrivate() 0041 : instance(new ServerManager(this)) 0042 , mState(ServerManager::NotRunning) 0043 , mSafetyTimer(new QTimer) 0044 { 0045 mState = instance->state(); 0046 mSafetyTimer->setSingleShot(true); 0047 mSafetyTimer->setInterval(30000); 0048 QObject::connect(mSafetyTimer.data(), &QTimer::timeout, instance, [this]() { 0049 timeout(); 0050 }); 0051 if (mState == ServerManager::Running && Internal::clientType() == Internal::User && !ServerManager::hasInstanceIdentifier()) { 0052 mFirstRunner = new Firstrun(instance); 0053 } 0054 } 0055 0056 ~ServerManagerPrivate() 0057 { 0058 delete instance; 0059 } 0060 0061 void checkStatusChanged() 0062 { 0063 setState(instance->state()); 0064 } 0065 0066 void setState(ServerManager::State state) 0067 { 0068 if (mState != state) { 0069 mState = state; 0070 Q_EMIT instance->stateChanged(state); 0071 if (state == ServerManager::Running) { 0072 Q_EMIT instance->started(); 0073 if (!mFirstRunner && Internal::clientType() == Internal::User && !ServerManager::hasInstanceIdentifier()) { 0074 mFirstRunner = new Firstrun(instance); 0075 } 0076 } else if (state == ServerManager::NotRunning || state == ServerManager::Broken) { 0077 Q_EMIT instance->stopped(); 0078 } 0079 if (state == ServerManager::Starting || state == ServerManager::Stopping) { 0080 QMetaObject::invokeMethod(mSafetyTimer.data(), qOverload<>(&QTimer::start), Qt::QueuedConnection); // in case we are in a different thread 0081 } else { 0082 QMetaObject::invokeMethod(mSafetyTimer.data(), &QTimer::stop, Qt::QueuedConnection); // in case we are in a different thread 0083 } 0084 } 0085 } 0086 0087 void timeout() 0088 { 0089 if (mState == ServerManager::Starting || mState == ServerManager::Stopping) { 0090 setState(ServerManager::Broken); 0091 } 0092 } 0093 0094 ServerManager *instance = nullptr; 0095 static int serverProtocolVersion; 0096 static uint generation; 0097 ServerManager::State mState; 0098 QScopedPointer<QTimer> mSafetyTimer; 0099 Firstrun *mFirstRunner = nullptr; 0100 static Internal::ClientType clientType; 0101 QString mBrokenReason; 0102 std::unique_ptr<QDBusServiceWatcher> serviceWatcher; 0103 }; 0104 0105 int ServerManagerPrivate::serverProtocolVersion = -1; 0106 uint ServerManagerPrivate::generation = 0; 0107 Internal::ClientType ServerManagerPrivate::clientType = Internal::User; 0108 0109 Q_GLOBAL_STATIC(ServerManagerPrivate, sInstance) // NOLINT(readability-redundant-member-init) 0110 0111 ServerManager::ServerManager(ServerManagerPrivate *dd) 0112 : d(dd) 0113 { 0114 qRegisterMetaType<Akonadi::ServerManager::State>(); 0115 0116 d->serviceWatcher = std::make_unique<QDBusServiceWatcher>(ServerManager::serviceName(ServerManager::Server), 0117 QDBusConnection::sessionBus(), 0118 QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration); 0119 d->serviceWatcher->addWatchedService(ServerManager::serviceName(ServerManager::Control)); 0120 d->serviceWatcher->addWatchedService(ServerManager::serviceName(ServerManager::ControlLock)); 0121 d->serviceWatcher->addWatchedService(ServerManager::serviceName(ServerManager::UpgradeIndicator)); 0122 0123 // this (and also the two connects below) are queued so that they trigger after AgentManager is done loading 0124 // the current agent types and instances 0125 // this ensures the invariant of AgentManager reporting a consistent state if ServerManager::state() == Running 0126 // that's the case with direct connections as well, but only after you enter the event loop once 0127 connect( 0128 d->serviceWatcher.get(), 0129 &QDBusServiceWatcher::serviceRegistered, 0130 this, 0131 [this]() { 0132 d->serverProtocolVersion = -1; 0133 d->checkStatusChanged(); 0134 }, 0135 Qt::QueuedConnection); 0136 connect( 0137 d->serviceWatcher.get(), 0138 &QDBusServiceWatcher::serviceUnregistered, 0139 this, 0140 [this](const QString &name) { 0141 if (name == ServerManager::serviceName(ServerManager::ControlLock) && d->mState == ServerManager::Starting) { 0142 // Control.Lock has disappeared during startup, which means that akonadi_control 0143 // has terminated, most probably because it was not able to start akonadiserver 0144 // process. Don't wait 30 seconds for sefetyTimeout, but go into Broken state 0145 // immediately. 0146 d->setState(ServerManager::Broken); 0147 return; 0148 } 0149 0150 d->serverProtocolVersion = -1; 0151 d->checkStatusChanged(); 0152 }, 0153 Qt::QueuedConnection); 0154 0155 // AgentManager is dangerous to use for agents themselves 0156 if (Internal::clientType() != Internal::User) { 0157 return; 0158 } 0159 0160 connect( 0161 AgentManager::self(), 0162 &AgentManager::typeAdded, 0163 this, 0164 [this]() { 0165 d->checkStatusChanged(); 0166 }, 0167 Qt::QueuedConnection); 0168 connect( 0169 AgentManager::self(), 0170 &AgentManager::typeRemoved, 0171 this, 0172 [this]() { 0173 d->checkStatusChanged(); 0174 }, 0175 Qt::QueuedConnection); 0176 } 0177 0178 ServerManager *Akonadi::ServerManager::self() 0179 { 0180 return sInstance->instance; 0181 } 0182 0183 bool ServerManager::start() 0184 { 0185 const bool controlRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Control)); 0186 const bool serverRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Server)); 0187 if (controlRegistered && serverRegistered) { 0188 return true; 0189 } 0190 0191 const bool controlLockRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::ControlLock)); 0192 if (controlLockRegistered || controlRegistered) { 0193 qCDebug(AKONADICORE_LOG) << "Akonadi server is already starting up"; 0194 sInstance->setState(Starting); 0195 return true; 0196 } 0197 0198 qCDebug(AKONADICORE_LOG) << "executing akonadi_control"; 0199 QStringList args; 0200 if (hasInstanceIdentifier()) { 0201 args << QStringLiteral("--instance") << instanceIdentifier(); 0202 } 0203 const QString exec = QStandardPaths::findExecutable(QStringLiteral("akonadi_control")); 0204 if (exec.isEmpty() || !QProcess::startDetached(exec, args)) { 0205 qCWarning(AKONADICORE_LOG) << "Unable to execute akonadi_control, falling back to D-Bus auto-launch"; 0206 QDBusReply<void> reply = QDBusConnection::sessionBus().interface()->startService(ServerManager::serviceName(ServerManager::Control)); 0207 if (!reply.isValid()) { 0208 qCDebug(AKONADICORE_LOG) << "Akonadi server could not be started via D-Bus either: " << reply.error().message(); 0209 return false; 0210 } 0211 } 0212 sInstance->setState(Starting); 0213 return true; 0214 } 0215 0216 bool ServerManager::stop() 0217 { 0218 QDBusInterface iface(ServerManager::serviceName(ServerManager::Control), 0219 QStringLiteral("/ControlManager"), 0220 QStringLiteral("org.freedesktop.Akonadi.ControlManager")); 0221 if (!iface.isValid()) { 0222 return false; 0223 } 0224 iface.call(QDBus::NoBlock, QStringLiteral("shutdown")); 0225 sInstance->setState(Stopping); 0226 return true; 0227 } 0228 0229 void ServerManager::showSelfTestDialog(QWidget *parent) 0230 { 0231 Q_UNUSED(parent) 0232 const QString exec = QStandardPaths::findExecutable(QStringLiteral("akonadiselftest")); 0233 if (exec.isEmpty() || !QProcess::startDetached(exec, QStringList())) { 0234 qCWarning(AKONADICORE_LOG) << "Could not find akonadiselftest in PATH."; 0235 } 0236 } 0237 0238 bool ServerManager::isRunning() 0239 { 0240 return state() == Running; 0241 } 0242 0243 ServerManager::State ServerManager::state() 0244 { 0245 ServerManager::State previousState = NotRunning; 0246 if (sInstance.exists()) { // be careful, this is called from the ServerManager::Private ctor, so using sInstance unprotected can cause infinite recursion 0247 previousState = sInstance->mState; 0248 sInstance->mBrokenReason.clear(); 0249 } 0250 0251 const bool serverUpgrading = QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::UpgradeIndicator)); 0252 if (serverUpgrading) { 0253 return Upgrading; 0254 } 0255 0256 const bool controlRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Control)); 0257 const bool serverRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Server)); 0258 if (controlRegistered && serverRegistered) { 0259 // check if the server protocol is recent enough 0260 if (sInstance.exists()) { 0261 if (Internal::serverProtocolVersion() >= 0 && Internal::serverProtocolVersion() != Protocol::version()) { 0262 sInstance->mBrokenReason = i18n( 0263 "The Akonadi server protocol version differs from the protocol version used by this application.\n" 0264 "If you recently updated your system please log out and back in to make sure all applications use the " 0265 "correct protocol version."); 0266 return Broken; 0267 } 0268 } 0269 0270 // AgentManager is dangerous to use for agents themselves 0271 if (Internal::clientType() == Internal::User) { 0272 // besides the running server processes we also need at least one resource to be operational 0273 const AgentType::List agentTypes = AgentManager::self()->types(); 0274 for (const AgentType &type : agentTypes) { 0275 if (type.capabilities().contains(QLatin1StringView("Resource"))) { 0276 return Running; 0277 } 0278 } 0279 if (sInstance.exists()) { 0280 sInstance->mBrokenReason = i18n("There are no Akonadi Agents available. Please verify your KDE PIM installation."); 0281 } 0282 return Broken; 0283 } else { 0284 return Running; 0285 } 0286 } 0287 0288 const bool controlLockRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::ControlLock)); 0289 if (controlLockRegistered || controlRegistered) { 0290 qCDebug(AKONADICORE_LOG) << "Akonadi server is only partially running. Server:" << serverRegistered << "ControlLock:" << controlLockRegistered 0291 << "Control:" << controlRegistered; 0292 if (previousState == Running) { 0293 return NotRunning; // we don't know if it's starting or stopping, probably triggered by someone else 0294 } 0295 return previousState; 0296 } 0297 0298 if (serverRegistered) { 0299 qCWarning(AKONADICORE_LOG) << "Akonadi server running without control process!"; 0300 return Broken; 0301 } 0302 0303 if (previousState == Starting) { // valid case where nothing is running (yet) 0304 return previousState; 0305 } 0306 return NotRunning; 0307 } 0308 0309 QString ServerManager::brokenReason() 0310 { 0311 if (sInstance.exists()) { 0312 return sInstance->mBrokenReason; 0313 } 0314 return QString(); 0315 } 0316 0317 QString ServerManager::instanceIdentifier() 0318 { 0319 return Instance::identifier(); 0320 } 0321 0322 bool ServerManager::hasInstanceIdentifier() 0323 { 0324 return Instance::hasIdentifier(); 0325 } 0326 0327 QString ServerManager::serviceName(ServerManager::ServiceType serviceType) 0328 { 0329 switch (serviceType) { 0330 case Server: 0331 return DBus::serviceName(DBus::Server); 0332 case Control: 0333 return DBus::serviceName(DBus::Control); 0334 case ControlLock: 0335 return DBus::serviceName(DBus::ControlLock); 0336 case UpgradeIndicator: 0337 return DBus::serviceName(DBus::UpgradeIndicator); 0338 } 0339 Q_ASSERT(!"WTF?"); 0340 return QString(); 0341 } 0342 0343 QString ServerManager::agentServiceName(ServiceAgentType agentType, const QString &identifier) 0344 { 0345 switch (agentType) { 0346 case Agent: 0347 return DBus::agentServiceName(identifier, DBus::Agent); 0348 case Resource: 0349 return DBus::agentServiceName(identifier, DBus::Resource); 0350 case Preprocessor: 0351 return DBus::agentServiceName(identifier, DBus::Preprocessor); 0352 } 0353 Q_ASSERT(!"WTF?"); 0354 return QString(); 0355 } 0356 0357 QString ServerManager::serverConfigFilePath(OpenMode openMode) 0358 { 0359 return StandardDirs::serverConfigFile(openMode == Akonadi::ServerManager::ReadOnly ? StandardDirs::ReadOnly : StandardDirs::ReadWrite); 0360 } 0361 0362 QString ServerManager::agentConfigFilePath(const QString &identifier) 0363 { 0364 return StandardDirs::agentConfigFile(identifier); 0365 } 0366 0367 QString ServerManager::addNamespace(const QString &string) 0368 { 0369 if (Instance::hasIdentifier()) { 0370 return string % QLatin1Char('_') % Instance::identifier(); 0371 } 0372 return string; 0373 } 0374 0375 uint ServerManager::generation() 0376 { 0377 return Internal::generation(); 0378 } 0379 0380 int Internal::serverProtocolVersion() 0381 { 0382 return ServerManagerPrivate::serverProtocolVersion; 0383 } 0384 0385 void Internal::setServerProtocolVersion(int version) 0386 { 0387 ServerManagerPrivate::serverProtocolVersion = version; 0388 if (sInstance.exists()) { 0389 sInstance->checkStatusChanged(); 0390 } 0391 } 0392 0393 uint Internal::generation() 0394 { 0395 return ServerManagerPrivate::generation; 0396 } 0397 0398 void Internal::setGeneration(uint generation) 0399 { 0400 ServerManagerPrivate::generation = generation; 0401 } 0402 0403 Internal::ClientType Internal::clientType() 0404 { 0405 return ServerManagerPrivate::clientType; 0406 } 0407 0408 void Internal::setClientType(ClientType type) 0409 { 0410 ServerManagerPrivate::clientType = type; 0411 } 0412 0413 #include "moc_servermanager.cpp"