Akonadi

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

KDE's Doxygen guidelines are available online.