1 | /* |
2 | Copyright (c) 2008 Volker Krause <vkrause@kde.org> |
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 "agentbase.h" |
25 | #include "agentmanager.h" |
26 | #include "dbusconnectionpool.h" |
27 | #include "selftestdialog_p.h" |
28 | #include "session_p.h" |
29 | #include "firstrun_p.h" |
30 | |
31 | #include <KDebug> |
32 | #include <KGlobal> |
33 | |
34 | #include <akonadi/private/protocol_p.h> |
35 | #include <akonadi/private/xdgbasedirs_p.h> |
36 | |
37 | #include <QtDBus> |
38 | #include <QPointer> |
39 | #include <QTimer> |
40 | |
41 | #include <boost/scoped_ptr.hpp> |
42 | |
43 | using namespace Akonadi; |
44 | |
45 | class Akonadi::ServerManagerPrivate |
46 | { |
47 | public: |
48 | ServerManagerPrivate() |
49 | : instance(new ServerManager(this)) |
50 | , mState(ServerManager::NotRunning) |
51 | , mSafetyTimer(new QTimer) |
52 | , mFirstRunner(0) |
53 | { |
54 | mState = instance->state(); |
55 | mSafetyTimer->setSingleShot(true); |
56 | mSafetyTimer->setInterval(30000); |
57 | QObject::connect(mSafetyTimer.get(), SIGNAL(timeout()), instance, SLOT(timeout())); |
58 | KGlobal::locale()->insertCatalog(QString::fromLatin1("libakonadi" )); |
59 | if (mState == ServerManager::Running && Internal::clientType() == Internal::User && !ServerManager::hasInstanceIdentifier()) { |
60 | mFirstRunner = new Firstrun(instance); |
61 | } |
62 | } |
63 | |
64 | ~ServerManagerPrivate() |
65 | { |
66 | delete instance; |
67 | } |
68 | |
69 | void serviceOwnerChanged( const QString &name, const QString &oldOwner, const QString &newOwner ) |
70 | { |
71 | if (name == ServerManager::serviceName(ServerManager::ControlLock ) && !oldOwner.isEmpty() && newOwner.isEmpty()) { |
72 | // Control.Lock has disappeared during startup, which means that akonadi_control |
73 | // has terminated, most probably because it was not able to start akonadiserver |
74 | // process. Don't wait 30 seconds for sefetyTimeout, but go into Broken state |
75 | // immediately. |
76 | if (mState == ServerManager::Starting) { |
77 | setState(ServerManager::Broken); |
78 | return; |
79 | } |
80 | } |
81 | |
82 | serverProtocolVersion = -1, |
83 | checkStatusChanged(); |
84 | } |
85 | |
86 | void checkStatusChanged() |
87 | { |
88 | setState(instance->state()); |
89 | } |
90 | |
91 | void setState(ServerManager::State state) |
92 | { |
93 | |
94 | if (mState != state) { |
95 | mState = state; |
96 | emit instance->stateChanged(state); |
97 | if (state == ServerManager::Running) { |
98 | emit instance->started(); |
99 | if (!mFirstRunner && Internal::clientType() == Internal::User && !ServerManager::hasInstanceIdentifier()) { |
100 | mFirstRunner = new Firstrun(instance); |
101 | } |
102 | } else if (state == ServerManager::NotRunning || state == ServerManager::Broken) { |
103 | emit instance->stopped(); |
104 | } |
105 | |
106 | if (state == ServerManager::Starting || state == ServerManager::Stopping) { |
107 | QMetaObject::invokeMethod(mSafetyTimer.get(), "start" , Qt::QueuedConnection); // in case we are in a different thread |
108 | } else { |
109 | QMetaObject::invokeMethod(mSafetyTimer.get(), "stop" , Qt::QueuedConnection); // in case we are in a different thread |
110 | } |
111 | } |
112 | } |
113 | |
114 | void timeout() |
115 | { |
116 | if (mState == ServerManager::Starting || mState == ServerManager::Stopping) { |
117 | setState(ServerManager::Broken); |
118 | } |
119 | } |
120 | |
121 | ServerManager *instance; |
122 | static int serverProtocolVersion; |
123 | ServerManager::State mState; |
124 | boost::scoped_ptr<QTimer> mSafetyTimer; |
125 | Firstrun *mFirstRunner; |
126 | static Internal::ClientType clientType; |
127 | }; |
128 | |
129 | int ServerManagerPrivate::serverProtocolVersion = -1; |
130 | Internal::ClientType ServerManagerPrivate::clientType = Internal::User; |
131 | |
132 | K_GLOBAL_STATIC(ServerManagerPrivate, sInstance) |
133 | |
134 | ServerManager::ServerManager(ServerManagerPrivate *dd) |
135 | : d(dd) |
136 | { |
137 | qRegisterMetaType<Akonadi::ServerManager::State>(); |
138 | |
139 | QDBusServiceWatcher *watcher = new QDBusServiceWatcher(ServerManager::serviceName(ServerManager::Server), |
140 | DBusConnectionPool::threadConnection(), |
141 | QDBusServiceWatcher::WatchForOwnerChange, this); |
142 | watcher->addWatchedService(ServerManager::serviceName(ServerManager::Control)); |
143 | watcher->addWatchedService(ServerManager::serviceName(ServerManager::ControlLock)); |
144 | watcher->addWatchedService(ServerManager::serviceName(ServerManager::UpgradeIndicator)); |
145 | |
146 | // this (and also the two connects below) are queued so that they trigger after AgentManager is done loading |
147 | // the current agent types and instances |
148 | // this ensures the invariant of AgentManager reporting a consistent state if ServerManager::state() == Running |
149 | // that's the case with direct connections as well, but only after you enter the event loop once |
150 | connect(watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)), |
151 | this, SLOT(serviceOwnerChanged(QString,QString,QString)), Qt::QueuedConnection); |
152 | |
153 | // AgentManager is dangerous to use for agents themselves |
154 | if (Internal::clientType() != Internal::User) { |
155 | return; |
156 | } |
157 | connect(AgentManager::self(), SIGNAL(typeAdded(Akonadi::AgentType)), SLOT(checkStatusChanged()), Qt::QueuedConnection); |
158 | connect(AgentManager::self(), SIGNAL(typeRemoved(Akonadi::AgentType)), SLOT(checkStatusChanged()), Qt::QueuedConnection); |
159 | } |
160 | |
161 | ServerManager *Akonadi::ServerManager::self() |
162 | { |
163 | return sInstance->instance; |
164 | } |
165 | |
166 | bool ServerManager::start() |
167 | { |
168 | const bool controlRegistered = DBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Control)); |
169 | const bool serverRegistered = DBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Server)); |
170 | if (controlRegistered && serverRegistered) { |
171 | return true; |
172 | } |
173 | |
174 | const bool controlLockRegistered = DBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::ControlLock)); |
175 | if (controlLockRegistered || controlRegistered) { |
176 | kDebug() << "Akonadi server is already starting up" ; |
177 | sInstance->setState(Starting); |
178 | return true; |
179 | } |
180 | |
181 | kDebug() << "executing akonadi_control" ; |
182 | QStringList args; |
183 | if (hasInstanceIdentifier()) { |
184 | args << QLatin1String("--instance" ) << instanceIdentifier(); |
185 | } |
186 | const bool ok = QProcess::startDetached(QLatin1String("akonadi_control" ), args); |
187 | if (!ok) { |
188 | kWarning() << "Unable to execute akonadi_control, falling back to D-Bus auto-launch" ; |
189 | QDBusReply<void> reply = DBusConnectionPool::threadConnection().interface()->startService(ServerManager::serviceName(ServerManager::Control)); |
190 | if (!reply.isValid()) { |
191 | kDebug() << "Akonadi server could not be started via D-Bus either: " |
192 | << reply.error().message(); |
193 | return false; |
194 | } |
195 | } |
196 | sInstance->setState(Starting); |
197 | return true; |
198 | } |
199 | |
200 | bool ServerManager::stop() |
201 | { |
202 | QDBusInterface iface(ServerManager::serviceName(ServerManager::Control), |
203 | QString::fromLatin1("/ControlManager" ), |
204 | QString::fromLatin1("org.freedesktop.Akonadi.ControlManager" )); |
205 | if (!iface.isValid()) { |
206 | return false; |
207 | } |
208 | iface.call(QDBus::NoBlock, QString::fromLatin1("shutdown" )); |
209 | sInstance->setState(Stopping); |
210 | return true; |
211 | } |
212 | |
213 | void ServerManager::showSelfTestDialog(QWidget *parent) |
214 | { |
215 | QPointer<Akonadi::SelfTestDialog> dlg(new Akonadi::SelfTestDialog(parent)); |
216 | dlg->hideIntroduction(); |
217 | dlg->exec(); |
218 | delete dlg; |
219 | } |
220 | |
221 | bool ServerManager::isRunning() |
222 | { |
223 | return state() == Running; |
224 | } |
225 | |
226 | ServerManager::State ServerManager::state() |
227 | { |
228 | ServerManager::State previousState = NotRunning; |
229 | if (sInstance.exists()) { // be careful, this is called from the ServerManager::Private ctor, so using sInstance unprotected can cause infinite recursion |
230 | previousState = sInstance->mState; |
231 | } |
232 | |
233 | const bool serverUpgrading = DBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::UpgradeIndicator)); |
234 | if (serverUpgrading) { |
235 | return Upgrading; |
236 | } |
237 | |
238 | const bool controlRegistered = DBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Control)); |
239 | const bool serverRegistered = DBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Server)); |
240 | if (controlRegistered && serverRegistered) { |
241 | // check if the server protocol is recent enough |
242 | if (sInstance.exists()) { |
243 | if (Internal::serverProtocolVersion() >= 0 && |
244 | Internal::serverProtocolVersion() < SessionPrivate::minimumProtocolVersion()) { |
245 | return Broken; |
246 | } |
247 | } |
248 | |
249 | // AgentManager is dangerous to use for agents themselves |
250 | if (Internal::clientType() == Internal::User) { |
251 | // besides the running server processes we also need at least one resource to be operational |
252 | AgentType::List agentTypes = AgentManager::self()->types(); |
253 | foreach (const AgentType &type, agentTypes) { |
254 | if (type.capabilities().contains(QLatin1String("Resource" ))) { |
255 | return Running; |
256 | } |
257 | } |
258 | return Broken; |
259 | } else { |
260 | return Running; |
261 | } |
262 | } |
263 | |
264 | const bool controlLockRegistered = DBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::ControlLock)); |
265 | if (controlLockRegistered || controlRegistered) { |
266 | kDebug() << "Akonadi server is already starting up" ; |
267 | if (previousState == Running) { |
268 | return NotRunning; // we don't know if it's starting or stopping, probably triggered by someone else |
269 | } |
270 | return previousState; |
271 | } |
272 | |
273 | if (serverRegistered) { |
274 | kWarning() << "Akonadi server running without control process!" ; |
275 | return Broken; |
276 | } |
277 | |
278 | if (previousState == Starting) { // valid case where nothing is running (yet) |
279 | return previousState; |
280 | } |
281 | return NotRunning; |
282 | } |
283 | |
284 | QString ServerManager::instanceIdentifier() |
285 | { |
286 | return QLatin1String(qgetenv("AKONADI_INSTANCE" )); |
287 | } |
288 | |
289 | bool ServerManager::hasInstanceIdentifier() |
290 | { |
291 | return !instanceIdentifier().isEmpty(); |
292 | } |
293 | |
294 | static QString makeServiceName(const char *base, const QString &name = QString()) |
295 | { |
296 | if (ServerManager::instanceIdentifier().isEmpty()) { |
297 | return QLatin1String(base) % name; |
298 | } |
299 | return QLatin1String(base) % name % QLatin1Literal("." ) % ServerManager::instanceIdentifier(); |
300 | } |
301 | |
302 | // remove once we require Akonadi 1.9 |
303 | #ifndef AKONADI_DBUS_SERVER_SERVICE_UPGRADING |
304 | #define AKONADI_DBUS_SERVER_SERVICE_UPGRADING "org.freedesktop.Akonadi.upgrading" |
305 | #endif |
306 | |
307 | QString ServerManager::serviceName(ServerManager::ServiceType serviceType) |
308 | { |
309 | switch (serviceType) { |
310 | case Server: |
311 | return makeServiceName(AKONADI_DBUS_SERVER_SERVICE); |
312 | case Control: |
313 | return makeServiceName(AKONADI_DBUS_CONTROL_SERVICE); |
314 | case ControlLock: |
315 | return makeServiceName(AKONADI_DBUS_CONTROL_SERVICE_LOCK); |
316 | case UpgradeIndicator: |
317 | return makeServiceName(AKONADI_DBUS_SERVER_SERVICE_UPGRADING); |
318 | } |
319 | Q_ASSERT(!"WTF?" ); |
320 | return QString(); |
321 | } |
322 | |
323 | QString ServerManager::agentServiceName(ServiceAgentType agentType, const QString &identifier) |
324 | { |
325 | switch (agentType) { |
326 | case Agent: |
327 | return makeServiceName(AKONADI_DBUS_SERVER_SERVICE, QString::fromLatin1(".Agent.%1" ).arg(identifier)); |
328 | case Resource: |
329 | return makeServiceName(AKONADI_DBUS_SERVER_SERVICE, QString::fromLatin1(".Resource.%1" ).arg(identifier)); |
330 | case Preprocessor: |
331 | return makeServiceName(AKONADI_DBUS_SERVER_SERVICE, QString::fromLatin1(".Preprocessor.%1" ).arg(identifier)); |
332 | } |
333 | Q_ASSERT(!"WTF?" ); |
334 | return QString(); |
335 | } |
336 | |
337 | QString ServerManager::addNamespace(const QString &string) |
338 | { |
339 | if (ServerManager::hasInstanceIdentifier()) { |
340 | return string % QLatin1Char('_') % ServerManager::instanceIdentifier(); |
341 | } |
342 | return string; |
343 | } |
344 | |
345 | int Internal::serverProtocolVersion() |
346 | { |
347 | return ServerManagerPrivate::serverProtocolVersion; |
348 | } |
349 | |
350 | void Internal::setServerProtocolVersion(int version) |
351 | { |
352 | ServerManagerPrivate::serverProtocolVersion = version; |
353 | if (sInstance.exists()) { |
354 | sInstance->checkStatusChanged(); |
355 | } |
356 | } |
357 | |
358 | Internal::ClientType Internal::clientType() |
359 | { |
360 | return ServerManagerPrivate::clientType; |
361 | } |
362 | |
363 | void Internal::setClientType(ClientType type) |
364 | { |
365 | ServerManagerPrivate::clientType = type; |
366 | } |
367 | |
368 | QString Internal::xdgSaveDir(const char *resource, const QString &relPath) |
369 | { |
370 | QString fullRelPath = QLatin1String("akonadi" ); |
371 | if (!ServerManager::instanceIdentifier().isEmpty()) { |
372 | fullRelPath += QLatin1String("/instance/" ) + ServerManager::instanceIdentifier(); |
373 | } |
374 | if (!relPath.isEmpty()) { |
375 | fullRelPath += QLatin1Char('/') + relPath; |
376 | } |
377 | return XdgBaseDirs::saveDir(resource, fullRelPath); |
378 | } |
379 | |
380 | #include "moc_servermanager.cpp" |
381 | |