/* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "servermanager.h" #include "servermanager_p.h" #include "agenttype.h" #include "agentbase.h" #include "agentmanager.h" #include "dbusconnectionpool.h" #include "selftestdialog_p.h" #include "session_p.h" #include "firstrun_p.h" #include #include #include #include #include #include #include #include #include using namespace Akonadi; class Akonadi::ServerManagerPrivate { public: ServerManagerPrivate() : instance(new ServerManager(this)) , mState(ServerManager::NotRunning) , mSafetyTimer(new QTimer) , mFirstRunner(0) { mState = instance->state(); mSafetyTimer->setSingleShot(true); mSafetyTimer->setInterval(30000); QObject::connect(mSafetyTimer.get(), SIGNAL(timeout()), instance, SLOT(timeout())); KGlobal::locale()->insertCatalog(QString::fromLatin1("libakonadi")); if (mState == ServerManager::Running && Internal::clientType() == Internal::User && !ServerManager::hasInstanceIdentifier()) { mFirstRunner = new Firstrun(instance); } } ~ServerManagerPrivate() { delete instance; } void serviceOwnerChanged( const QString &name, const QString &oldOwner, const QString &newOwner ) { if (name == ServerManager::serviceName(ServerManager::ControlLock ) && !oldOwner.isEmpty() && newOwner.isEmpty()) { // Control.Lock has disappeared during startup, which means that akonadi_control // has terminated, most probably because it was not able to start akonadiserver // process. Don't wait 30 seconds for sefetyTimeout, but go into Broken state // immediately. if (mState == ServerManager::Starting) { setState(ServerManager::Broken); return; } } serverProtocolVersion = -1, checkStatusChanged(); } void checkStatusChanged() { setState(instance->state()); } void setState(ServerManager::State state) { if (mState != state) { mState = state; emit instance->stateChanged(state); if (state == ServerManager::Running) { emit instance->started(); if (!mFirstRunner && Internal::clientType() == Internal::User && !ServerManager::hasInstanceIdentifier()) { mFirstRunner = new Firstrun(instance); } } else if (state == ServerManager::NotRunning || state == ServerManager::Broken) { emit instance->stopped(); } if (state == ServerManager::Starting || state == ServerManager::Stopping) { QMetaObject::invokeMethod(mSafetyTimer.get(), "start", Qt::QueuedConnection); // in case we are in a different thread } else { QMetaObject::invokeMethod(mSafetyTimer.get(), "stop", Qt::QueuedConnection); // in case we are in a different thread } } } void timeout() { if (mState == ServerManager::Starting || mState == ServerManager::Stopping) { setState(ServerManager::Broken); } } ServerManager *instance; static int serverProtocolVersion; ServerManager::State mState; boost::scoped_ptr mSafetyTimer; Firstrun *mFirstRunner; static Internal::ClientType clientType; }; int ServerManagerPrivate::serverProtocolVersion = -1; Internal::ClientType ServerManagerPrivate::clientType = Internal::User; K_GLOBAL_STATIC(ServerManagerPrivate, sInstance) ServerManager::ServerManager(ServerManagerPrivate *dd) : d(dd) { qRegisterMetaType(); QDBusServiceWatcher *watcher = new QDBusServiceWatcher(ServerManager::serviceName(ServerManager::Server), DBusConnectionPool::threadConnection(), QDBusServiceWatcher::WatchForOwnerChange, this); watcher->addWatchedService(ServerManager::serviceName(ServerManager::Control)); watcher->addWatchedService(ServerManager::serviceName(ServerManager::ControlLock)); watcher->addWatchedService(ServerManager::serviceName(ServerManager::UpgradeIndicator)); // this (and also the two connects below) are queued so that they trigger after AgentManager is done loading // the current agent types and instances // this ensures the invariant of AgentManager reporting a consistent state if ServerManager::state() == Running // that's the case with direct connections as well, but only after you enter the event loop once connect(watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)), this, SLOT(serviceOwnerChanged(QString,QString,QString)), Qt::QueuedConnection); // AgentManager is dangerous to use for agents themselves if (Internal::clientType() != Internal::User) { return; } connect(AgentManager::self(), SIGNAL(typeAdded(Akonadi::AgentType)), SLOT(checkStatusChanged()), Qt::QueuedConnection); connect(AgentManager::self(), SIGNAL(typeRemoved(Akonadi::AgentType)), SLOT(checkStatusChanged()), Qt::QueuedConnection); } ServerManager *Akonadi::ServerManager::self() { return sInstance->instance; } bool ServerManager::start() { const bool controlRegistered = DBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Control)); const bool serverRegistered = DBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Server)); if (controlRegistered && serverRegistered) { return true; } const bool controlLockRegistered = DBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::ControlLock)); if (controlLockRegistered || controlRegistered) { kDebug() << "Akonadi server is already starting up"; sInstance->setState(Starting); return true; } kDebug() << "executing akonadi_control"; QStringList args; if (hasInstanceIdentifier()) { args << QLatin1String("--instance") << instanceIdentifier(); } const bool ok = QProcess::startDetached(QLatin1String("akonadi_control"), args); if (!ok) { kWarning() << "Unable to execute akonadi_control, falling back to D-Bus auto-launch"; QDBusReply reply = DBusConnectionPool::threadConnection().interface()->startService(ServerManager::serviceName(ServerManager::Control)); if (!reply.isValid()) { kDebug() << "Akonadi server could not be started via D-Bus either: " << reply.error().message(); return false; } } sInstance->setState(Starting); return true; } bool ServerManager::stop() { QDBusInterface iface(ServerManager::serviceName(ServerManager::Control), QString::fromLatin1("/ControlManager"), QString::fromLatin1("org.freedesktop.Akonadi.ControlManager")); if (!iface.isValid()) { return false; } iface.call(QDBus::NoBlock, QString::fromLatin1("shutdown")); sInstance->setState(Stopping); return true; } void ServerManager::showSelfTestDialog(QWidget *parent) { QPointer dlg(new Akonadi::SelfTestDialog(parent)); dlg->hideIntroduction(); dlg->exec(); delete dlg; } bool ServerManager::isRunning() { return state() == Running; } ServerManager::State ServerManager::state() { ServerManager::State previousState = NotRunning; if (sInstance.exists()) { // be careful, this is called from the ServerManager::Private ctor, so using sInstance unprotected can cause infinite recursion previousState = sInstance->mState; } const bool serverUpgrading = DBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::UpgradeIndicator)); if (serverUpgrading) { return Upgrading; } const bool controlRegistered = DBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Control)); const bool serverRegistered = DBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Server)); if (controlRegistered && serverRegistered) { // check if the server protocol is recent enough if (sInstance.exists()) { if (Internal::serverProtocolVersion() >= 0 && Internal::serverProtocolVersion() < SessionPrivate::minimumProtocolVersion()) { return Broken; } } // AgentManager is dangerous to use for agents themselves if (Internal::clientType() == Internal::User) { // besides the running server processes we also need at least one resource to be operational AgentType::List agentTypes = AgentManager::self()->types(); foreach (const AgentType &type, agentTypes) { if (type.capabilities().contains(QLatin1String("Resource"))) { return Running; } } return Broken; } else { return Running; } } const bool controlLockRegistered = DBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::ControlLock)); if (controlLockRegistered || controlRegistered) { kDebug() << "Akonadi server is already starting up"; if (previousState == Running) { return NotRunning; // we don't know if it's starting or stopping, probably triggered by someone else } return previousState; } if (serverRegistered) { kWarning() << "Akonadi server running without control process!"; return Broken; } if (previousState == Starting) { // valid case where nothing is running (yet) return previousState; } return NotRunning; } QString ServerManager::instanceIdentifier() { return QLatin1String(qgetenv("AKONADI_INSTANCE")); } bool ServerManager::hasInstanceIdentifier() { return !instanceIdentifier().isEmpty(); } static QString makeServiceName(const char *base, const QString &name = QString()) { if (ServerManager::instanceIdentifier().isEmpty()) { return QLatin1String(base) % name; } return QLatin1String(base) % name % QLatin1Literal(".") % ServerManager::instanceIdentifier(); } // remove once we require Akonadi 1.9 #ifndef AKONADI_DBUS_SERVER_SERVICE_UPGRADING #define AKONADI_DBUS_SERVER_SERVICE_UPGRADING "org.freedesktop.Akonadi.upgrading" #endif QString ServerManager::serviceName(ServerManager::ServiceType serviceType) { switch (serviceType) { case Server: return makeServiceName(AKONADI_DBUS_SERVER_SERVICE); case Control: return makeServiceName(AKONADI_DBUS_CONTROL_SERVICE); case ControlLock: return makeServiceName(AKONADI_DBUS_CONTROL_SERVICE_LOCK); case UpgradeIndicator: return makeServiceName(AKONADI_DBUS_SERVER_SERVICE_UPGRADING); } Q_ASSERT(!"WTF?"); return QString(); } QString ServerManager::agentServiceName(ServiceAgentType agentType, const QString &identifier) { switch (agentType) { case Agent: return makeServiceName(AKONADI_DBUS_SERVER_SERVICE, QString::fromLatin1(".Agent.%1").arg(identifier)); case Resource: return makeServiceName(AKONADI_DBUS_SERVER_SERVICE, QString::fromLatin1(".Resource.%1").arg(identifier)); case Preprocessor: return makeServiceName(AKONADI_DBUS_SERVER_SERVICE, QString::fromLatin1(".Preprocessor.%1").arg(identifier)); } Q_ASSERT(!"WTF?"); return QString(); } QString ServerManager::addNamespace(const QString &string) { if (ServerManager::hasInstanceIdentifier()) { return string % QLatin1Char('_') % ServerManager::instanceIdentifier(); } return string; } int Internal::serverProtocolVersion() { return ServerManagerPrivate::serverProtocolVersion; } void Internal::setServerProtocolVersion(int version) { ServerManagerPrivate::serverProtocolVersion = version; if (sInstance.exists()) { sInstance->checkStatusChanged(); } } Internal::ClientType Internal::clientType() { return ServerManagerPrivate::clientType; } void Internal::setClientType(ClientType type) { ServerManagerPrivate::clientType = type; } QString Internal::xdgSaveDir(const char *resource, const QString &relPath) { QString fullRelPath = QLatin1String("akonadi"); if (!ServerManager::instanceIdentifier().isEmpty()) { fullRelPath += QLatin1String("/instance/") + ServerManager::instanceIdentifier(); } if (!relPath.isEmpty()) { fullRelPath += QLatin1Char('/') + relPath; } return XdgBaseDirs::saveDir(resource, fullRelPath); } #include "moc_servermanager.cpp"