Akonadi

servermanager.cpp
1/*
2 SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "servermanager.h"
8#include "servermanager_p.h"
9
10#include "agentmanager.h"
11#include "agenttype.h"
12#include "firstrun_p.h"
13#include "session_p.h"
14
15#include "akonadicore_debug.h"
16
17#include <KLocalizedString>
18
19#include "private/dbus_p.h"
20#include "private/instance_p.h"
21#include "private/protocol_p.h"
22#include "private/standarddirs_p.h"
23
24#include <QDBusConnection>
25#include <QDBusConnectionInterface>
26#include <QDBusInterface>
27#include <QDBusReply>
28#include <QDBusServiceWatcher>
29#include <QProcess>
30#include <QScopedPointer>
31#include <QStandardPaths>
32#include <QTimer>
33#include <qnamespace.h>
34
35using namespace Akonadi;
36
37class Akonadi::ServerManagerPrivate
38{
39public:
40 ServerManagerPrivate()
41 : instance(new ServerManager(this))
42 , mState(ServerManager::NotRunning)
43 , mSafetyTimer(new QTimer)
44 {
45 mState = instance->state();
46 mSafetyTimer->setSingleShot(true);
47 mSafetyTimer->setInterval(30000);
48 QObject::connect(mSafetyTimer.data(), &QTimer::timeout, instance, [this]() {
49 timeout();
50 });
51 if (mState == ServerManager::Running && Internal::clientType() == Internal::User && !ServerManager::hasInstanceIdentifier()) {
52 mFirstRunner = new Firstrun(instance);
53 }
54 }
55
56 ~ServerManagerPrivate()
57 {
58 delete instance;
59 }
60
61 void checkStatusChanged()
62 {
63 setState(instance->state());
64 }
65
66 void setState(ServerManager::State state)
67 {
68 if (mState != state) {
69 mState = state;
70 Q_EMIT instance->stateChanged(state);
71 if (state == ServerManager::Running) {
72 Q_EMIT instance->started();
73 if (!mFirstRunner && Internal::clientType() == Internal::User && !ServerManager::hasInstanceIdentifier()) {
74 mFirstRunner = new Firstrun(instance);
75 }
76 } else if (state == ServerManager::NotRunning || state == ServerManager::Broken) {
77 Q_EMIT instance->stopped();
78 }
79 if (state == ServerManager::Starting || state == ServerManager::Stopping) {
80 QMetaObject::invokeMethod(mSafetyTimer.data(), qOverload<>(&QTimer::start), Qt::QueuedConnection); // in case we are in a different thread
81 } else {
82 QMetaObject::invokeMethod(mSafetyTimer.data(), &QTimer::stop, Qt::QueuedConnection); // in case we are in a different thread
83 }
84 }
85 }
86
87 void timeout()
88 {
89 if (mState == ServerManager::Starting || mState == ServerManager::Stopping) {
90 setState(ServerManager::Broken);
91 }
92 }
93
94 ServerManager *instance = nullptr;
95 static int serverProtocolVersion;
96 static uint generation;
98 QScopedPointer<QTimer> mSafetyTimer;
99 Firstrun *mFirstRunner = nullptr;
100 static Internal::ClientType clientType;
101 QString mBrokenReason;
102 std::unique_ptr<QDBusServiceWatcher> serviceWatcher;
103};
104
105int ServerManagerPrivate::serverProtocolVersion = -1;
106uint ServerManagerPrivate::generation = 0;
107Internal::ClientType ServerManagerPrivate::clientType = Internal::User;
108
109Q_GLOBAL_STATIC(ServerManagerPrivate, sInstance) // NOLINT(readability-redundant-member-init)
110
111ServerManager::ServerManager(ServerManagerPrivate *dd)
112 : d(dd)
113{
114 qRegisterMetaType<Akonadi::ServerManager::State>();
115
116 d->serviceWatcher = std::make_unique<QDBusServiceWatcher>(ServerManager::serviceName(ServerManager::Server),
119 d->serviceWatcher->addWatchedService(ServerManager::serviceName(ServerManager::Control));
120 d->serviceWatcher->addWatchedService(ServerManager::serviceName(ServerManager::ControlLock));
121 d->serviceWatcher->addWatchedService(ServerManager::serviceName(ServerManager::UpgradeIndicator));
122
123 // this (and also the two connects below) are queued so that they trigger after AgentManager is done loading
124 // the current agent types and instances
125 // this ensures the invariant of AgentManager reporting a consistent state if ServerManager::state() == Running
126 // that's the case with direct connections as well, but only after you enter the event loop once
127 connect(
128 d->serviceWatcher.get(),
130 this,
131 [this]() {
132 d->serverProtocolVersion = -1;
133 d->checkStatusChanged();
134 },
136 connect(
137 d->serviceWatcher.get(),
139 this,
140 [this](const QString &name) {
141 if (name == ServerManager::serviceName(ServerManager::ControlLock) && d->mState == ServerManager::Starting) {
142 // Control.Lock has disappeared during startup, which means that akonadi_control
143 // has terminated, most probably because it was not able to start akonadiserver
144 // process. Don't wait 30 seconds for sefetyTimeout, but go into Broken state
145 // immediately.
146 d->setState(ServerManager::Broken);
147 return;
148 }
149
150 d->serverProtocolVersion = -1;
151 d->checkStatusChanged();
152 },
154
155 // AgentManager is dangerous to use for agents themselves
156 if (Internal::clientType() != Internal::User) {
157 return;
158 }
159
160 connect(
161 AgentManager::self(),
162 &AgentManager::typeAdded,
163 this,
164 [this]() {
165 d->checkStatusChanged();
166 },
168 connect(
169 AgentManager::self(),
170 &AgentManager::typeRemoved,
171 this,
172 [this]() {
173 d->checkStatusChanged();
174 },
176}
177
179{
180 return sInstance->instance;
181}
182
184{
188 return true;
189 }
190
193 qCDebug(AKONADICORE_LOG) << "Akonadi server is already starting up";
194 sInstance->setState(Starting);
195 return true;
196 }
197
198 qCDebug(AKONADICORE_LOG) << "executing akonadi_control";
200 if (hasInstanceIdentifier()) {
201 args << QStringLiteral("--instance") << instanceIdentifier();
202 }
203 const QString exec = QStandardPaths::findExecutable(QStringLiteral("akonadi_control"));
204 if (exec.isEmpty() || !QProcess::startDetached(exec, args)) {
205 qCWarning(AKONADICORE_LOG) << "Unable to execute akonadi_control, falling back to D-Bus auto-launch";
207 if (!reply.isValid()) {
208 qCDebug(AKONADICORE_LOG) << "Akonadi server could not be started via D-Bus either: " << reply.error().message();
209 return false;
210 }
211 }
212 sInstance->setState(Starting);
213 return true;
214}
215
217{
218 QDBusInterface iface(ServerManager::serviceName(ServerManager::Control),
219 QStringLiteral("/ControlManager"),
220 QStringLiteral("org.freedesktop.Akonadi.ControlManager"));
221 if (!iface.isValid()) {
222 return false;
223 }
224 iface.call(QDBus::NoBlock, QStringLiteral("shutdown"));
225 sInstance->setState(Stopping);
226 return true;
227}
228
230{
232 const QString exec = QStandardPaths::findExecutable(QStringLiteral("akonadiselftest"));
233 if (exec.isEmpty() || !QProcess::startDetached(exec, QStringList())) {
234 qCWarning(AKONADICORE_LOG) << "Could not find akonadiselftest in PATH.";
235 }
236}
237
239{
240 return state() == Running;
241}
242
244{
246 if (sInstance.exists()) { // be careful, this is called from the ServerManager::Private ctor, so using sInstance unprotected can cause infinite recursion
247 previousState = sInstance->mState;
248 sInstance->mBrokenReason.clear();
249 }
250
252 if (serverUpgrading) {
253 return Upgrading;
254 }
255
259 // check if the server protocol is recent enough
260 if (sInstance.exists()) {
261 if (Internal::serverProtocolVersion() >= 0 && Internal::serverProtocolVersion() != Protocol::version()) {
262 sInstance->mBrokenReason = i18n(
263 "The Akonadi server protocol version differs from the protocol version used by this application.\n"
264 "If you recently updated your system please log out and back in to make sure all applications use the "
265 "correct protocol version.");
266 return Broken;
267 }
268 }
269
270 // AgentManager is dangerous to use for agents themselves
271 if (Internal::clientType() == Internal::User) {
272 // besides the running server processes we also need at least one resource to be operational
273 const AgentType::List agentTypes = AgentManager::self()->types();
274 for (const AgentType &type : agentTypes) {
275 if (type.capabilities().contains(QLatin1StringView("Resource"))) {
276 return Running;
277 }
278 }
279 if (sInstance.exists()) {
280 sInstance->mBrokenReason = i18n("There are no Akonadi Agents available. Please verify your KDE PIM installation.");
281 }
282 return Broken;
283 } else {
284 return Running;
285 }
286 }
287
290 qCDebug(AKONADICORE_LOG) << "Akonadi server is only partially running. Server:" << serverRegistered << "ControlLock:" << controlLockRegistered
291 << "Control:" << controlRegistered;
292 if (previousState == Running) {
293 return NotRunning; // we don't know if it's starting or stopping, probably triggered by someone else
294 }
295 return previousState;
296 }
297
298 if (serverRegistered) {
299 qCWarning(AKONADICORE_LOG) << "Akonadi server running without control process!";
300 return Broken;
301 }
302
303 if (previousState == Starting) { // valid case where nothing is running (yet)
304 return previousState;
305 }
306 return NotRunning;
307}
308
310{
311 if (sInstance.exists()) {
312 return sInstance->mBrokenReason;
313 }
314 return QString();
315}
316
318{
319 return Instance::identifier();
320}
321
323{
324 return Instance::hasIdentifier();
325}
326
328{
329 switch (serviceType) {
330 case Server:
331 return DBus::serviceName(DBus::Server);
332 case Control:
333 return DBus::serviceName(DBus::Control);
334 case ControlLock:
335 return DBus::serviceName(DBus::ControlLock);
336 case UpgradeIndicator:
337 return DBus::serviceName(DBus::UpgradeIndicator);
338 }
339 Q_ASSERT(!"WTF?");
340 return QString();
341}
342
344{
345 switch (agentType) {
346 case Agent:
347 return DBus::agentServiceName(identifier, DBus::Agent);
348 case Resource:
349 return DBus::agentServiceName(identifier, DBus::Resource);
350 case Preprocessor:
351 return DBus::agentServiceName(identifier, DBus::Preprocessor);
352 }
353 Q_ASSERT(!"WTF?");
354 return QString();
355}
356
358{
359 return StandardDirs::serverConfigFile(openMode == Akonadi::ServerManager::ReadOnly ? StandardDirs::ReadOnly : StandardDirs::ReadWrite);
360}
361
363{
364 return StandardDirs::agentConfigFile(identifier);
365}
366
368{
369 if (Instance::hasIdentifier()) {
370 return string % QLatin1Char('_') % Instance::identifier();
371 }
372 return string;
373}
374
376{
377 return Internal::generation();
378}
379
380int Internal::serverProtocolVersion()
381{
382 return ServerManagerPrivate::serverProtocolVersion;
383}
384
385void Internal::setServerProtocolVersion(int version)
386{
387 ServerManagerPrivate::serverProtocolVersion = version;
388 if (sInstance.exists()) {
389 sInstance->checkStatusChanged();
390 }
391}
392
393uint Internal::generation()
394{
395 return ServerManagerPrivate::generation;
396}
397
398void Internal::setGeneration(uint generation)
399{
400 ServerManagerPrivate::generation = generation;
401}
402
403Internal::ClientType Internal::clientType()
404{
405 return ServerManagerPrivate::clientType;
406}
407
408void Internal::setClientType(ClientType type)
409{
410 ServerManagerPrivate::clientType = type;
411}
412
413#include "moc_servermanager.cpp"
AgentType::List types() const
Returns the list of all available agent types.
static AgentManager * self()
Returns the global instance of the agent manager.
A representation of an agent type.
Provides methods to control the Akonadi server process.
Definition control.h:54
Provides methods to control the Akonadi server process.
static QString serverConfigFilePath(OpenMode openMode)
Returns absolute path to akonadiserverrc file with Akonadi server configuration.
static State state()
Returns the state of the server.
static QString brokenReason()
Returns the reason why the Server is broken, if known.
static bool isRunning()
Checks if the server is available currently.
void started()
Emitted whenever the server becomes fully operational.
static QString agentServiceName(ServiceAgentType agentType, const QString &identifier)
Returns the namespaced D-Bus service name for an agent of type agentType with agent identifier identi...
static bool start()
Starts the server.
ServiceType
Types of known D-Bus services.
static bool hasInstanceIdentifier()
Returns true if we are connected to a non-default Akonadi server instance.
static uint generation()
Returns current Akonadi database generation identifier.
static bool stop()
Stops the server.
void stopped()
Emitted whenever the server becomes unavailable.
ServiceAgentType
Known agent types.
static QString addNamespace(const QString &string)
Adds the multi-instance namespace to string if required (with '_' as separator).
State
Enum for the various states the server can be in.
@ Running
Server is running and operational.
@ Starting
Server was started but is not yet running.
@ Broken
Server is not operational and an error has been detected.
@ Upgrading
Server is performing a database upgrade as part of a new startup.
@ NotRunning
Server is not running, could be no one started it yet or it failed to start.
@ Stopping
Server is shutting down.
void stateChanged(Akonadi::ServerManager::State state)
Emitted whenever the server state changes.
static ServerManager * self()
Returns the singleton instance of this class, for connecting to its signals.
static QString agentConfigFilePath(const QString &identifier)
Returns absolute path to configuration file of an agent identified by given identifier.
static QString instanceIdentifier()
Returns the identifier of the Akonadi instance we are connected to.
static QString serviceName(ServiceType serviceType)
Returns the namespaced D-Bus service name for serviceType.
static void showSelfTestDialog(QWidget *parent)
Shows the Akonadi self test dialog, which tests Akonadi for various problems and reports these to the...
QString i18n(const char *text, const TYPE &arg...)
Helper integration between Akonadi and Qt.
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
QDBusConnectionInterface * interface() const const
QDBusConnection sessionBus()
QDBusReply< bool > isServiceRegistered(const QString &serviceName) const const
QDBusReply< void > startService(const QString &name)
QString message() const const
const QDBusError & error()
bool isValid() const const
void serviceRegistered(const QString &serviceName)
void serviceUnregistered(const QString &serviceName)
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
T qobject_cast(QObject *object)
bool startDetached(qint64 *pid)
T * data() const const
QString findExecutable(const QString &executableName, const QStringList &paths)
bool isEmpty() const const
QueuedConnection
void start()
void stop()
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sun Feb 25 2024 18:38:51 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.