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
211
212
213
214
215
216
217
218
219
220
221
222
223
|
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "launcherpackets.h"
#include <QtCore/qobject.h>
#include <QHash>
#include <QMutex>
#include <QMutexLocker>
#include <QProcess>
#include <QWaitCondition>
#include <vector>
#include <atomic>
QT_BEGIN_NAMESPACE
class QLocalSocket;
QT_END_NAMESPACE
namespace Utils {
class LauncherInterface;
namespace Internal {
class LauncherInterfacePrivate;
class CallerHandle;
// Moved to the launcher thread, returned to caller's thread.
// It's assumed that this object will be alive at least
// as long as the corresponding QtcProcess is alive.
// We should have LauncherSocket::registerHandle() and LauncherSocket::unregisterHandle()
// methods.
class LauncherHandle : public QObject
{
Q_OBJECT
public:
// called from main thread
bool waitForStarted(int msecs) // TODO: we might already be in finished state when calling this method - fix it!
{ return waitForState(msecs, WaitingForState::Started, QProcess::Running); }
bool waitForFinished(int msecs)
{ return waitForState(msecs, WaitingForState::Finished, QProcess::NotRunning); }
QProcess::ProcessState state() const
{ QMutexLocker locker(&m_mutex); return m_processState; }
void cancel();
bool isCanceled() const { return m_canceled; }
QByteArray readAllStandardOutput()
{ QMutexLocker locker(&m_mutex); return readAndClear(m_stdout); }
QByteArray readAllStandardError()
{ QMutexLocker locker(&m_mutex); return readAndClear(m_stderr); }
qint64 processId() const { QMutexLocker locker(&m_mutex); return m_processId; }
QString errorString() const { QMutexLocker locker(&m_mutex); return m_errorString; }
void setErrorString(const QString &str) { QMutexLocker locker(&m_mutex); m_errorString = str; }
// Called from other thread. Create a temp object receiver which lives in caller's thread.
// Add started and finished signals to it and post a flush to it.
// When we are in waitForState() which is called from the same thread,
// we may flush the signal queue and emit these signals immediately.
// Who should remove this object? deleteLater()?
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode);
QProcess::ProcessError error() const { QMutexLocker locker(&m_mutex); return m_error; }
QString program() const { QMutexLocker locker(&m_mutex); return m_command; }
void setStandardInputFile(const QString &fileName) { QMutexLocker locker(&m_mutex); m_standardInputFile = fileName; }
void setProcessChannelMode(QProcess::ProcessChannelMode mode) {
QMutexLocker locker(&m_mutex);
if (mode != QProcess::SeparateChannels && mode != QProcess::MergedChannels) {
qWarning("setProcessChannelMode: The only supported modes are SeparateChannels and MergedChannels.");
return;
}
m_channelMode = mode;
}
void setProcessEnvironment(const QProcessEnvironment &environment)
{ QMutexLocker locker(&m_mutex); m_environment = environment; }
void setWorkingDirectory(const QString &dir) { QMutexLocker locker(&m_mutex); m_workingDirectory = dir; }
QProcess::ExitStatus exitStatus() const { QMutexLocker locker(&m_mutex); return m_exitStatus; }
signals:
void errorOccurred(QProcess::ProcessError error);
void started();
void finished(int exitCode, QProcess::ExitStatus status);
void readyReadStandardOutput();
void readyReadStandardError();
private:
enum class WaitingForState {
Idle,
Started,
ReadyRead,
Finished
};
// called from other thread
bool waitForState(int msecs, WaitingForState newState, QProcess::ProcessState targetState);
bool doWaitForState(int msecs, WaitingForState newState, QProcess::ProcessState targetState);
void doStart();
void slotStarted();
void slotReadyRead();
void slotFinished();
// called from this thread
LauncherHandle(quintptr token) : m_token(token) {}
void createCallerHandle();
void destroyCallerHandle();
void flushCaller();
void handlePacket(LauncherPacketType type, const QByteArray &payload);
void handleErrorPacket(const QByteArray &packetData);
void handleStartedPacket(const QByteArray &packetData);
void handleFinishedPacket(const QByteArray &packetData);
void handleSocketReady();
void handleSocketError(const QString &message);
void stateReached(WaitingForState wakeUpState, QProcess::ProcessState newState);
QByteArray readAndClear(QByteArray &data)
{
const QByteArray tmp = data;
data.clear();
return tmp;
}
void sendPacket(const Internal::LauncherPacket &packet);
mutable QMutex m_mutex;
QWaitCondition m_waitCondition;
const quintptr m_token;
WaitingForState m_waitingFor = WaitingForState::Idle;
QProcess::ProcessState m_processState = QProcess::NotRunning;
std::atomic_bool m_canceled = false;
std::atomic_bool m_failed = false;
std::atomic_bool m_finished = false;
int m_processId = 0;
int m_exitCode = 0;
QProcess::ExitStatus m_exitStatus = QProcess::ExitStatus::NormalExit;
QByteArray m_stdout;
QByteArray m_stderr;
QString m_errorString;
QProcess::ProcessError m_error = QProcess::UnknownError;
bool m_socketError = false;
QString m_command;
QStringList m_arguments;
QProcessEnvironment m_environment;
QString m_workingDirectory;
QIODevice::OpenMode m_openMode = QIODevice::ReadWrite;
QProcess::ProcessChannelMode m_channelMode = QProcess::SeparateChannels;
QString m_standardInputFile;
CallerHandle *m_callerHandle = nullptr;
friend class LauncherSocket;
};
class LauncherSocket : public QObject
{
Q_OBJECT
friend class LauncherInterfacePrivate;
public:
bool isReady() const { return m_socket.load(); }
void sendData(const QByteArray &data);
LauncherHandle *registerHandle(quintptr token);
void unregisterHandle(quintptr token);
signals:
void ready();
void errorOccurred(const QString &error);
private:
LauncherSocket(QObject *parent = nullptr);
LauncherHandle *handleForToken(quintptr token) const;
void setSocket(QLocalSocket *socket);
void shutdown();
void handleSocketError();
void handleSocketDataAvailable();
void handleSocketDisconnected();
void handleError(const QString &error);
void handleRequests();
std::atomic<QLocalSocket *> m_socket{nullptr};
PacketParser m_packetParser;
std::vector<QByteArray> m_requests;
mutable QMutex m_mutex;
QHash<quintptr, LauncherHandle *> m_handles;
};
} // namespace Internal
} // namespace Utils
|