kde-extraapps/kdevplatform/shell/sessionlock.cpp
2015-07-26 14:23:17 +03:00

222 lines
8.3 KiB
C++

/*
* This file is part of KDevelop
* Copyright 2013 Milian Wolff <mail@milianw.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3, or any
* later version accepted by the membership of KDE e.V. (or its
* successor approved by the membership of KDE e.V.), which shall
* act as a proxy defined in Section 6 of version 3 of the license.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "sessionlock.h"
#include "sessioncontroller.h"
#include <KDebug>
#include <KLocalizedString>
#include <KMessageBox>
#include <QDBusConnectionInterface>
#include <QFile>
using namespace KDevelop;
namespace {
QString lockFileForSession( const QString& id )
{
return SessionController::sessionDirectory( id ) + "/lock";
}
QString dBusServiceNameForSession( const QString& id )
{
// We remove starting "{" and ending "}" from the string UUID representation
// as D-Bus apparently doesn't allow them in service names
return QString( "org.kdevelop.kdevplatform-lock-" ) + QString( id ).mid( 1, id.size() - 2 );
}
/// Tries to own the lock-file and returns result
KLockFile::LockResult attemptRelock(KLockFile::Ptr lockFile)
{
return lockFile->lock( KLockFile::ForceFlag | KLockFile::NoBlockFlag );
}
/// Force-removes the lock-file.
void forceRemoveLockfile(const QString& lockFilename)
{
if( QFile::exists( lockFilename ) ) {
QFile::remove( lockFilename );
}
}
}
TryLockSessionResult SessionLock::tryLockSession(const QString& sessionId, bool doLocking)
{
///FIXME: if this is hit, someone tried to lock a non-existing session
/// this should be fixed by using a proper data type distinct from
/// QString for id's, i.e. QUuid or similar.
Q_ASSERT(QFile::exists(SessionController::sessionDirectory( sessionId )));
/*
* We've got two locking mechanisms here: D-Bus unique service name (based on the session id)
* and a plain lockfile (KLockFile).
* The latter is required to get the appname/pid of the locking instance
* in case if it's stale/hanging/crashed (to make user know which PID he needs to kill).
* D-Bus mechanism is the primary one.
*
* Since there is a kind of "logic tree", the code is a bit hard.
*/
const QString service = dBusServiceNameForSession( sessionId );
QDBusConnection connection = QDBusConnection::sessionBus();
QDBusConnectionInterface* connectionInterface = connection.interface();
const QString lockFilename = lockFileForSession( sessionId );
KLockFile::Ptr lockFile(new KLockFile( lockFilename ));
bool canLockDBus = !connectionInterface->isServiceRegistered( service );
bool lockedDBus = false;
// Lock D-Bus if we can and we need to
if( doLocking && canLockDBus ) {
lockedDBus = connection.registerService( service );
}
// Attempt to lock file, despite the possibility to do so and presence of the request (doLocking)
// This is required as KLockFile::getLockInfo() works only after KLockFile::lock() is called
KLockFile::LockResult lockResult = attemptRelock(lockFile);
SessionRunInfo runInfo;
if( lockResult == KLockFile::LockOK ) {
// Unlock immediately if we shouldn't have locked it
if( !lockedDBus ) {
lockFile->unlock();
}
} else {
// If locking failed, retrieve the lock's metadata
lockFile->getLockInfo( runInfo.holderPid, runInfo.holderHostname, runInfo.holderApp );
if( lockedDBus ) {
// Since the lock-file is secondary, try to force-lock it if D-Bus locking succeeded
forceRemoveLockfile(lockFilename);
lockResult = attemptRelock(lockFile);
Q_ASSERT(lockResult == KLockFile::LockOK);
}
}
// Set the result by D-Bus status
if (doLocking && lockedDBus) {
return TryLockSessionResult(QSharedPointer<ISessionLock>(new SessionLock(sessionId, lockFile)));
} else {
runInfo.isRunning = !canLockDBus;
return TryLockSessionResult(runInfo);
}
}
QString SessionLock::id()
{
return m_sessionId;
}
SessionLock::SessionLock(const QString& sessionId, const KLockFile::Ptr& lockFile)
: m_sessionId(sessionId)
, m_lockFile(lockFile)
{
Q_ASSERT(lockFile->isLocked());
}
SessionLock::~SessionLock()
{
m_lockFile->unlock();
bool unregistered = QDBusConnection::sessionBus().unregisterService( dBusServiceNameForSession(m_sessionId) );
Q_ASSERT(unregistered);
Q_UNUSED(unregistered);
}
QString SessionLock::handleLockedSession(const QString& sessionName, const QString& sessionId,
const SessionRunInfo& runInfo)
{
if( !runInfo.isRunning ) {
return sessionId;
}
// try to make the locked session active
{
// The timeout for "ensureVisible" call
// Leave it sufficiently low to avoid waiting for hung instances.
static const int timeout_ms = 1000;
QDBusMessage message = QDBusMessage::createMethodCall( dBusServiceNameForSession(sessionId),
"/kdevelop/MainWindow",
"org.kdevelop.MainWindow",
"ensureVisible" );
QDBusMessage reply = QDBusConnection::sessionBus().call( message,
QDBus::Block,
timeout_ms );
if( reply.type() == QDBusMessage::ReplyMessage ) {
QTextStream out(stdout);
out << i18nc( "@info:shell", "made running %1 instance (PID: %2) visible", runInfo.holderApp, runInfo.holderPid ) << endl;
return QString();
} else {
kWarning() << i18nc("@info:shell", "running %1 instance (PID: %2) is apparently hung", runInfo.holderApp, runInfo.holderPid);
}
}
// otherwise ask the user whether we should retry
QString problemDescription = i18nc("@info",
"The given application did not respond to a DBUS call, "
"it may have crashed or is hanging.");
QString problemHeader;
if( runInfo.holderPid != -1 ) {
problemHeader = i18nc("@info", "Failed to lock the session <em>%1</em>, "
"already locked by %2 on %3 (PID %4).",
sessionName, runInfo.holderApp, runInfo.holderHostname, runInfo.holderPid);
} else {
problemHeader = i18nc("@info", "Failed to lock the session <em>%1</em> (lock-file unavailable).",
sessionName);
}
QString problemResolution = i18nc("@info", "<p>Please, close the offending application instance "
"or choose another session to launch.</p>");
QString errmsg = "<p>" + problemHeader + "<br>" + problemDescription + "</p>" + problemResolution;
KGuiItem retry = KStandardGuiItem::cont();
retry.setText(i18nc("@action:button", "Retry startup"));
KGuiItem choose = KStandardGuiItem::configure();
choose.setText(i18nc("@action:button", "Choose another session"));
KGuiItem cancel = KStandardGuiItem::quit();
int ret = KMessageBox::warningYesNoCancel(0, errmsg, i18nc("@title:window", "Failed to Lock Session %1", sessionName),
retry, choose, cancel);
switch( ret ) {
case KMessageBox::Yes:
return sessionId;
break;
case KMessageBox::No: {
QString errmsg = i18nc("@info", "The session %1 is already active in another running instance.",
sessionName);
return SessionController::showSessionChooserDialog(errmsg);
break;
}
case KMessageBox::Cancel:
default:
break;
}
return QString();
}