File: sessionlock.cpp

package info (click to toggle)
kdevelop 4%3A22.12.2-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 70,096 kB
  • sloc: cpp: 284,635; javascript: 3,558; python: 3,422; sh: 1,319; ansic: 685; xml: 331; php: 95; lisp: 66; makefile: 39; sed: 12
file content (210 lines) | stat: -rw-r--r-- 8,093 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/*
    SPDX-FileCopyrightText: 2013 Milian Wolff <mail@milianw.de>

    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/

#include "sessionlock.h"

#include "debug.h"
#include "sessioncontroller.h"

#include <KLocalizedString>
#include <KMessageBox>
#include <KMessageBox_KDevCompat>

#include <QDBusConnectionInterface>
#include <QFile>
#include <QDir>

namespace KDevelop {
QString lockFileForSession( const QString& id )
{
    return SessionController::sessionDirectory( id ) + QLatin1String("/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 QLatin1String("org.kdevelop.kdevplatform-lock-") + id.midRef(1, id.size() - 2);
}

/// 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 (QLockFile).
     * 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 );
    QSharedPointer<QLockFile> lockFile(new QLockFile( lockFilename ));

    const bool haveDBus = connection.isConnected();
    const bool canLockDBus = haveDBus && connectionInterface && !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 QLockFile::getLockInfo() works only after QLockFile::lock() is called
    bool lockResult = lockFile->tryLock();
    SessionRunInfo runInfo;
    if (lockResult) {
        // Unlock immediately if we shouldn't have locked it
        if( haveDBus && !lockedDBus ) {
            lockFile->unlock();
        }
    } else {
        // If locking failed, retrieve the lock's metadata
        lockFile->getLockInfo(&runInfo.holderPid, &runInfo.holderHostname, &runInfo.holderApp );
        runInfo.isRunning = !haveDBus || !canLockDBus;

        if( haveDBus && lockedDBus ) {
            // Since the lock-file is secondary, try to force-lock it if D-Bus locking succeeded
            forceRemoveLockfile(lockFilename);
            lockResult = lockFile->tryLock();
            Q_ASSERT(lockResult);
        }
    }

    // Set the result by D-Bus status
    if (doLocking && (haveDBus ? lockedDBus : lockResult)) {
        return TryLockSessionResult(QSharedPointer<ISessionLock>(new SessionLock(sessionId, lockFile)));
    } else {
        return TryLockSessionResult(runInfo);
    }
}

QString SessionLock::id()
{
    return m_sessionId;
}

SessionLock::SessionLock(const QString& sessionId, const QSharedPointer<QLockFile>& 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_UNUSED(unregistered);
}

void SessionLock::removeFromDisk()
{
    Q_ASSERT(m_lockFile->isLocked());
    // unlock first to prevent warnings: "Could not remove our own lock file ..."
    m_lockFile->unlock();
    QDir(SessionController::sessionDirectory(m_sessionId)).removeRecursively();
}

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),
                                                                QStringLiteral("/kdevelop/MainWindow"),
                                                                QStringLiteral("org.kdevelop.MainWindow"),
                                                                QStringLiteral("ensureVisible") );
        QDBusMessage reply = QDBusConnection::sessionBus().call( message,
                                                                    QDBus::Block,
                                                                    timeout_ms );
        if( reply.type() == QDBusMessage::ReplyMessage ) {
            QTextStream out(stdout);
            out << i18nc(
                "@info:shell",
                "Running %1 instance (PID: %2) detected, making this one visible instead of starting a new one",
                runInfo.holderApp, runInfo.holderPid)
                << Qt::endl;
            return QString();
        } else {
            qCWarning(SHELL) << 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 = QLatin1String("<p>") + problemHeader + QLatin1String("<br>") + problemDescription + QLatin1String("</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::warningTwoActionsCancel(
        nullptr, errmsg, i18nc("@title:window", "Failed to Lock Session %1", sessionName), retry, choose, cancel);
    switch( ret ) {
    case KMessageBox::PrimaryAction:
        return sessionId;

    case KMessageBox::SecondaryAction: {
        QString errmsg = i18nc("@info", "The session %1 is already active in another running instance.",
                               sessionName);
        return SessionController::showSessionChooserDialog(errmsg);
    }

    case KMessageBox::Cancel:
    default:
        break;
    }

    return QString();
}

}