Akonadi

servermanager.cpp
1 /*
2  Copyright (c) 2008 Volker Krause <[email protected]>
3 
4  This library is free software; you can redistribute it and/or modify it
5  under the terms of the GNU Library General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or (at your
7  option) any later version.
8 
9  This library is distributed in the hope that it will be useful, but WITHOUT
10  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12  License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to the
16  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  02110-1301, USA.
18 */
19 
20 #include "servermanager.h"
21 #include "servermanager_p.h"
22 
23 #include "agenttype.h"
24 #include "agentmanager.h"
25 #include "session_p.h"
26 #include "firstrun_p.h"
27 
28 #include "akonadicore_debug.h"
29 
30 #include <Kdelibs4ConfigMigrator>
31 #include <KLocalizedString>
32 
33 #include "private/protocol_p.h"
34 #include "private/standarddirs_p.h"
35 #include "private/dbus_p.h"
36 #include "private/instance_p.h"
37 
38 #include <QDBusConnection>
39 #include <QDBusServiceWatcher>
40 #include <QDBusConnectionInterface>
41 #include <QDBusInterface>
42 #include <QDBusReply>
43 #include <QProcess>
44 #include <QTimer>
45 #include <QScopedPointer>
46 #include <qnamespace.h>
47 
48 using namespace Akonadi;
49 
50 class Akonadi::ServerManagerPrivate
51 {
52 public:
53  ServerManagerPrivate()
54  : instance(new ServerManager(this))
55  , mState(ServerManager::NotRunning)
56  , mSafetyTimer(new QTimer)
57  {
58  mState = instance->state();
59  mSafetyTimer->setSingleShot(true);
60  mSafetyTimer->setInterval(30000);
61  QObject::connect(mSafetyTimer.data(), &QTimer::timeout, instance, [this]() { timeout();});
62  if (mState == ServerManager::Running && Internal::clientType() == Internal::User && !ServerManager::hasInstanceIdentifier()) {
63  mFirstRunner = new Firstrun(instance);
64  }
65  }
66 
67  ~ServerManagerPrivate()
68  {
69  delete instance;
70  }
71 
72  void checkStatusChanged()
73  {
74  setState(instance->state());
75  }
76 
77  void setState(ServerManager::State state)
78  {
79  if (mState != state) {
80  mState = state;
81  Q_EMIT instance->stateChanged(state);
82  if (state == ServerManager::Running) {
83  Q_EMIT instance->started();
84  if (!mFirstRunner && Internal::clientType() == Internal::User && !ServerManager::hasInstanceIdentifier()) {
85  mFirstRunner = new Firstrun(instance);
86  }
87  } else if (state == ServerManager::NotRunning || state == ServerManager::Broken) {
88  Q_EMIT instance->stopped();
89  }
90  if (state == ServerManager::Starting || state == ServerManager::Stopping) {
91  QMetaObject::invokeMethod(mSafetyTimer.data(), QOverload<>::of(&QTimer::start), Qt::QueuedConnection); // in case we are in a different thread
92  } else {
93  QMetaObject::invokeMethod(mSafetyTimer.data(), &QTimer::stop, Qt::QueuedConnection); // in case we are in a different thread
94  }
95  }
96  }
97 
98  void timeout()
99  {
100  if (mState == ServerManager::Starting || mState == ServerManager::Stopping) {
101  setState(ServerManager::Broken);
102  }
103  }
104 
105  ServerManager *instance = nullptr;
106  static int serverProtocolVersion;
107  static uint generation;
108  ServerManager::State mState;
109  QScopedPointer<QTimer> mSafetyTimer;
110  Firstrun *mFirstRunner = nullptr;
111  static Internal::ClientType clientType;
112  QString mBrokenReason;
113  std::unique_ptr<QDBusServiceWatcher> serviceWatcher;
114 };
115 
116 int ServerManagerPrivate::serverProtocolVersion = -1;
117 uint ServerManagerPrivate::generation = 0;
118 Internal::ClientType ServerManagerPrivate::clientType = Internal::User;
119 
120 Q_GLOBAL_STATIC(ServerManagerPrivate, sInstance)
121 
122 ServerManager::ServerManager(ServerManagerPrivate *dd)
123  : d(dd)
124 {
125  Kdelibs4ConfigMigrator migrate(QStringLiteral("servermanager"));
126  migrate.setConfigFiles(QStringList() << QStringLiteral("akonadi-firstrunrc"));
127  migrate.migrate();
128 
129  qRegisterMetaType<Akonadi::ServerManager::State>();
130 
131  d->serviceWatcher = std::make_unique<QDBusServiceWatcher>(
133  QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration);
134  d->serviceWatcher->addWatchedService(ServerManager::serviceName(ServerManager::Control));
135  d->serviceWatcher->addWatchedService(ServerManager::serviceName(ServerManager::ControlLock));
136  d->serviceWatcher->addWatchedService(ServerManager::serviceName(ServerManager::UpgradeIndicator));
137 
138  // this (and also the two connects below) are queued so that they trigger after AgentManager is done loading
139  // the current agent types and instances
140  // this ensures the invariant of AgentManager reporting a consistent state if ServerManager::state() == Running
141  // that's the case with direct connections as well, but only after you enter the event loop once
142  connect(d->serviceWatcher.get(), &QDBusServiceWatcher::serviceRegistered,
143  this, [this]() {
144  d->serverProtocolVersion = -1;
145  d->checkStatusChanged();
146  }, Qt::QueuedConnection);
147  connect(d->serviceWatcher.get(), &QDBusServiceWatcher::serviceUnregistered,
148  this, [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  }, Qt::QueuedConnection);
161 
162  // AgentManager is dangerous to use for agents themselves
163  if (Internal::clientType() != Internal::User) {
164  return;
165  }
166 
167  connect(AgentManager::self(), &AgentManager::typeAdded, this, [this]() { d->checkStatusChanged(); }, Qt::QueuedConnection);
168  connect(AgentManager::self(), &AgentManager::typeRemoved, this, [this]() { d->checkStatusChanged(); }, Qt::QueuedConnection);
169 }
170 
172 {
173  return sInstance->instance;
174 }
175 
177 {
178  const bool controlRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Control));
179  const bool serverRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Server));
180  if (controlRegistered && serverRegistered) {
181  return true;
182  }
183 
184  const bool controlLockRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::ControlLock));
185  if (controlLockRegistered || controlRegistered) {
186  qCDebug(AKONADICORE_LOG) << "Akonadi server is already starting up";
187  sInstance->setState(Starting);
188  return true;
189  }
190 
191  qCDebug(AKONADICORE_LOG) << "executing akonadi_control";
192  QStringList args;
193  if (hasInstanceIdentifier()) {
194  args << QStringLiteral("--instance") << instanceIdentifier();
195  }
196  const bool ok = QProcess::startDetached(QStringLiteral("akonadi_control"), args);
197  if (!ok) {
198  qCWarning(AKONADICORE_LOG) << "Unable to execute akonadi_control, falling back to D-Bus auto-launch";
200  if (!reply.isValid()) {
201  qCDebug(AKONADICORE_LOG) << "Akonadi server could not be started via D-Bus either: "
202  << reply.error().message();
203  return false;
204  }
205  }
206  sInstance->setState(Starting);
207  return true;
208 }
209 
211 {
212  QDBusInterface iface(ServerManager::serviceName(ServerManager::Control),
213  QStringLiteral("/ControlManager"),
214  QStringLiteral("org.freedesktop.Akonadi.ControlManager"));
215  if (!iface.isValid()) {
216  return false;
217  }
218  iface.call(QDBus::NoBlock, QStringLiteral("shutdown"));
219  sInstance->setState(Stopping);
220  return true;
221 }
222 
224 {
225  Q_UNUSED(parent);
226  QProcess::startDetached(QStringLiteral("akonadiselftest"), QStringList());
227 }
228 
230 {
231  return state() == Running;
232 }
233 
235 {
236  ServerManager::State previousState = NotRunning;
237  if (sInstance.exists()) { // be careful, this is called from the ServerManager::Private ctor, so using sInstance unprotected can cause infinite recursion
238  previousState = sInstance->mState;
239  sInstance->mBrokenReason.clear();
240  }
241 
242  const bool serverUpgrading = QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::UpgradeIndicator));
243  if (serverUpgrading) {
244  return Upgrading;
245  }
246 
247  const bool controlRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Control));
248  const bool serverRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Server));
249  if (controlRegistered && serverRegistered) {
250  // check if the server protocol is recent enough
251  if (sInstance.exists()) {
252  if (Internal::serverProtocolVersion() >= 0 &&
253  Internal::serverProtocolVersion() != Protocol::version()) {
254  sInstance->mBrokenReason = i18n("The Akonadi server protocol version differs from the protocol version used by this application.\n"
255  "If you recently updated your system please log out and back in to make sure all applications use the "
256  "correct protocol version.");
257  return Broken;
258  }
259  }
260 
261  // AgentManager is dangerous to use for agents themselves
262  if (Internal::clientType() == Internal::User) {
263  // besides the running server processes we also need at least one resource to be operational
264  const AgentType::List agentTypes = AgentManager::self()->types();
265  for (const AgentType &type : agentTypes) {
266  if (type.capabilities().contains(QLatin1String("Resource"))) {
267  return Running;
268  }
269  }
270  if (sInstance.exists()) {
271  sInstance->mBrokenReason = i18n("There are no Akonadi Agents available. Please verify your KDE PIM installation.");
272  }
273  return Broken;
274  } else {
275  return Running;
276  }
277  }
278 
279  const bool controlLockRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::ControlLock));
280  if (controlLockRegistered || controlRegistered) {
281  qCDebug(AKONADICORE_LOG) << "Akonadi server is only partially running. Server:" << serverRegistered << "ControlLock:" << controlLockRegistered << "Control:" << controlRegistered;
282  if (previousState == Running) {
283  return NotRunning; // we don't know if it's starting or stopping, probably triggered by someone else
284  }
285  return previousState;
286  }
287 
288  if (serverRegistered) {
289  qCWarning(AKONADICORE_LOG) << "Akonadi server running without control process!";
290  return Broken;
291  }
292 
293  if (previousState == Starting) { // valid case where nothing is running (yet)
294  return previousState;
295  }
296  return NotRunning;
297 }
298 
300 {
301  if (sInstance.exists()) {
302  return sInstance->mBrokenReason;
303  }
304  return QString();
305 }
306 
308 {
309  return Instance::identifier();
310 }
311 
313 {
314  return Instance::hasIdentifier();
315 }
316 
318 {
319  switch (serviceType) {
320  case Server:
321  return DBus::serviceName(DBus::Server);
322  case Control:
323  return DBus::serviceName(DBus::Control);
324  case ControlLock:
325  return DBus::serviceName(DBus::ControlLock);
326  case UpgradeIndicator:
327  return DBus::serviceName(DBus::UpgradeIndicator);
328  }
329  Q_ASSERT(!"WTF?");
330  return QString();
331 }
332 
334 {
335  switch (agentType) {
336  case Agent:
337  return DBus::agentServiceName(identifier, DBus::Agent);
338  case Resource:
339  return DBus::agentServiceName(identifier, DBus::Resource);
340  case Preprocessor:
341  return DBus::agentServiceName(identifier, DBus::Preprocessor);
342  }
343  Q_ASSERT(!"WTF?");
344  return QString();
345 }
346 
348 {
349  return StandardDirs::serverConfigFile(
350  openMode == Akonadi::ServerManager::ReadOnly
351  ? StandardDirs::ReadOnly
352  : StandardDirs::ReadWrite);
353 }
354 
356 {
357  return StandardDirs::agentConfigFile(identifier);
358 }
359 
361 {
362  if (Instance::hasIdentifier()) {
363  return string % QLatin1Char('_') % Instance::identifier();
364  }
365  return string;
366 }
367 
369 {
370  return Internal::generation();
371 }
372 
373 int Internal::serverProtocolVersion()
374 {
375  return ServerManagerPrivate::serverProtocolVersion;
376 }
377 
378 void Internal::setServerProtocolVersion(int version)
379 {
380  ServerManagerPrivate::serverProtocolVersion = version;
381  if (sInstance.exists()) {
382  sInstance->checkStatusChanged();
383  }
384 }
385 
386 uint Internal::generation()
387 {
388  return ServerManagerPrivate::generation;
389 }
390 
391 void Internal::setGeneration(uint generation)
392 {
393  ServerManagerPrivate::generation = generation;
394 }
395 
396 Internal::ClientType Internal::clientType()
397 {
398  return ServerManagerPrivate::clientType;
399 }
400 
401 void Internal::setClientType(ClientType type)
402 {
403  ServerManagerPrivate::clientType = type;
404 }
405 
406 #include "moc_servermanager.cpp"
bool isValid() const const
void serviceRegistered(const QString &serviceName)
QDBusReply< void > startService(const QString &name)
QString name(const QVariant &location)
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.
ServiceAgentType
Known agent types.
static QString brokenReason()
Returns the reason why the Server is broken, if known.
QDBusConnectionInterface * interface() const const
bool startDetached(qint64 *pid)
static ServerManager * self()
Returns the singleton instance of this class, for connecting to its signals.
Provides methods to control the Akonadi server process.
Definition: control.h:64
QString message() const const
bool isValid() const const
QDBusConnection sessionBus()
static QString serviceName(ServiceType serviceType)
Returns the namespaced D-Bus service name for serviceType.
static uint generation()
Returns current Akonadi database generation identifier.
QDBusReply< bool > isServiceRegistered(const QString &serviceName) const const
Provides methods to control the Akonadi server process.
Definition: servermanager.h:43
void timeout()
void typeRemoved(const Akonadi::AgentType &type)
This signal is emitted whenever an agent type was removed from the system.
A representation of an agent type.
Server is shutting down.
Definition: servermanager.h:55
static void showSelfTestDialog(QWidget *parent)
Shows the Akonadi self test dialog, which tests Akonadi for various problems and reports these to the...
static State state()
Returns the state of the server.
void serviceUnregistered(const QString &serviceName)
Server is not running, could be no one started it yet or it failed to start.
Definition: servermanager.h:52
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.
Server was started but is not yet running.
Definition: servermanager.h:53
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)
const QDBusError & error()
QString i18n(const char *text, const TYPE &arg...)
Server is running and operational.
Definition: servermanager.h:54
AgentType::List types() const
Returns the list of all available agent types.
Helper integration between Akonadi and Qt.
ServiceType
Types of known D-Bus services.
static AgentManager * self()
Returns the global instance of the agent manager.
State
Enum for the various states the server can be in.
Definition: servermanager.h:51
static QString agentConfigFilePath(const QString &identifier)
Returns absolute path to configuration file of an agent identified by given identifier.
static bool stop()
Stops the server.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
static QString addNamespace(const QString &string)
Adds the multi-instance namespace to string if required (with &#39;_&#39; as separator).
void typeAdded(const Akonadi::AgentType &type)
This signal is emitted whenever a new agent type was installed on the system.
static bool isRunning()
Checks if the server is available currently.
static bool hasInstanceIdentifier()
Returns true if we are connected to a non-default Akonadi server instance.
Server is not operational and an error has been detected.
Definition: servermanager.h:56
void start()
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Thu Jun 4 2020 23:08:43 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.