aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp
blob: ea73682131ab440fdaa4cd635de98037d9069217 (plain)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/****************************************************************************
**
** Copyright (C) 2017 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.
**
****************************************************************************/

#include "vcsbasediffeditorcontroller.h"
#include "vcsbaseclient.h"
#include "vcscommand.h"

#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/progressmanager/progressmanager.h>

#include <diffeditor/diffutils.h>

#include <utils/commandline.h>
#include <utils/environment.h>
#include <utils/qtcassert.h>
#include <utils/runextensions.h>

#include <QPointer>

using namespace DiffEditor;
using namespace Core;
using namespace Utils;

namespace VcsBase {

static void readPatch(QFutureInterface<QList<FileData>> &futureInterface,
                      const QString &patch)
{
    bool ok;
    const QList<FileData> &fileDataList = DiffUtils::readPatch(patch, &ok, &futureInterface);
    futureInterface.reportResult(fileDataList);
}

/////////////////////

// We need a way to disconnect from signals posted from different thread
// so that signals that are already posted from the other thread and not delivered
// yet will be ignored. Unfortunately, simple QObject::disconnect() doesn't
// work like that, since signals that are already posted and are awaiting
// to be delivered WILL BE delivered later, even after a call to QObject::disconnect().
// The delivery will happen when the control returns to the main event loop.

// This proxy class solves the above problem. Instead of a call to
// QObject::disconnect(), which would still deliver posted signals,
// we delete the proxy object immediately. In this way signals which are
// already posted and are awaiting to be delivered won't be delivered to the
// destroyed object.

// So the only reason for this proxy object is to be able to disconnect
// effectively from the signals posted from different threads.

class VcsCommandResultProxy : public QObject {
    Q_OBJECT
public:
    VcsCommandResultProxy(VcsCommand *command, VcsBaseDiffEditorControllerPrivate *target);
private:
    void storeOutput(const QString &output);
    void commandFinished(bool success);

    VcsBaseDiffEditorControllerPrivate *m_target;
};

/////////////////////

class VcsBaseDiffEditorControllerPrivate
{
public:
    VcsBaseDiffEditorControllerPrivate(VcsBaseDiffEditorController *q) : q(q) {}
    ~VcsBaseDiffEditorControllerPrivate();

    void processingFinished();
    void processDiff(const QString &patch);
    void cancelReload();
    void storeOutput(const QString &output);
    void commandFinished(bool success);

    VcsBaseDiffEditorController *q;
    FilePath m_directory;
    Environment m_processEnvironment;
    FilePath m_vcsBinary;
    int m_vscTimeoutS;
    QString m_startupFile;
    QString m_output;
    QString m_displayName;
    QPointer<VcsCommand> m_command;
    QPointer<VcsCommandResultProxy> m_commandResultProxy;
    QFutureWatcher<QList<FileData>> *m_processWatcher = nullptr;
};

/////////////////////

VcsCommandResultProxy::VcsCommandResultProxy(VcsCommand *command,
                          VcsBaseDiffEditorControllerPrivate *target)
    : QObject(target->q)
    , m_target(target)
{
    connect(command, &VcsCommand::stdOutText,
            this, &VcsCommandResultProxy::storeOutput);
    connect(command, &VcsCommand::finished,
            this, &VcsCommandResultProxy::commandFinished);
    connect(command, &VcsCommand::destroyed,
            this, &QObject::deleteLater);
}

void VcsCommandResultProxy::storeOutput(const QString &output)
{
    m_target->storeOutput(output);
}

void VcsCommandResultProxy::commandFinished(bool success)
{
    m_target->commandFinished(success);
}

VcsBaseDiffEditorControllerPrivate::~VcsBaseDiffEditorControllerPrivate()
{
    cancelReload();
}

void VcsBaseDiffEditorControllerPrivate::processingFinished()
{
    QTC_ASSERT(m_processWatcher, return);

    // success is false when the user clicked the cancel micro button
    // inside the progress indicator
    const bool success = !m_processWatcher->future().isCanceled();
    const QList<FileData> fileDataList = success
            ? m_processWatcher->future().result() : QList<FileData>();

    // Prevent direct deletion of m_processWatcher since
    // processingFinished() is called directly by the m_processWatcher.
    m_processWatcher->deleteLater();
    m_processWatcher = nullptr;

    q->setDiffFiles(fileDataList, q->workingDirectory(), q->startupFile());
    q->reloadFinished(success);
}

void VcsBaseDiffEditorControllerPrivate::processDiff(const QString &patch)
{
    cancelReload();

    m_processWatcher = new QFutureWatcher<QList<FileData>>();

    QObject::connect(m_processWatcher, &QFutureWatcher<QList<FileData>>::finished,
                     [this] () { processingFinished(); } );

    m_processWatcher->setFuture(Utils::runAsync(&readPatch, patch));

    ProgressManager::addTask(m_processWatcher->future(),
                             VcsBaseDiffEditorController::tr("Processing diff"), "DiffEditor");
}

void VcsBaseDiffEditorControllerPrivate::cancelReload()
{
    if (m_command) {
        m_command->cancel();
        m_command.clear();
    }

    // Disconnect effectively, don't deliver already posted signals
    if (m_commandResultProxy)
        delete m_commandResultProxy.data();

    if (m_processWatcher) {
        // Cancel the running process without the further processingFinished()
        // notification for this process.
        m_processWatcher->future().cancel();
        delete m_processWatcher;
        m_processWatcher = nullptr;
    }

    m_output = QString();
}

void VcsBaseDiffEditorControllerPrivate::storeOutput(const QString &output)
{
    m_output = output;
}

void VcsBaseDiffEditorControllerPrivate::commandFinished(bool success)
{
    if (m_command)
        m_command.clear();

    // Prevent direct deletion of m_commandResultProxy inside the possible
    // subsequent synchronous calls to cancelReload() [called e.g. by
    // processCommandOutput() overload], since
    // commandFinished() is called directly by the m_commandResultProxy.
    // m_commandResultProxy is removed via deleteLater right after
    // a call to this commandFinished() is finished
    if (m_commandResultProxy)
        m_commandResultProxy.clear();

    if (!success) {
        cancelReload();
        q->reloadFinished(success);
        return;
    }

    q->processCommandOutput(QString(m_output)); // pass a copy of m_output
}

/////////////////////

VcsBaseDiffEditorController::VcsBaseDiffEditorController(Core::IDocument *document)
    : DiffEditorController(document)
    , d(new VcsBaseDiffEditorControllerPrivate(this))
{}

VcsBaseDiffEditorController::~VcsBaseDiffEditorController()
{
    delete d;
}

void VcsBaseDiffEditorController::runCommand(const QList<QStringList> &args, unsigned flags, QTextCodec *codec)
{
    // Cancel the possible ongoing reload without the commandFinished() nor
    // processingFinished() notifications, as right after that
    // we re-reload it from scratch. So no intermediate "Retrieving data failed."
    // and "Waiting for data..." will be shown.
    d->cancelReload();

    d->m_command = VcsBaseClient::createVcsCommand(workingDirectory(), d->m_processEnvironment);
    d->m_command->setDisplayName(d->m_displayName);
    d->m_command->setCodec(codec ? codec : EditorManager::defaultTextCodec());
    d->m_commandResultProxy = new VcsCommandResultProxy(d->m_command.data(), d);
    d->m_command->addFlags(flags);

    for (const QStringList &arg : args) {
        QTC_ASSERT(!arg.isEmpty(), continue);

        d->m_command->addJob({d->m_vcsBinary, arg}, d->m_vscTimeoutS);
    }

    d->m_command->execute();
}

void VcsBaseDiffEditorController::processCommandOutput(const QString &output)
{
    d->processDiff(output);
}

FilePath VcsBaseDiffEditorController::workingDirectory() const
{
    return d->m_directory;
}

void VcsBaseDiffEditorController::setStartupFile(const QString &startupFile)
{
    d->m_startupFile = startupFile;
}

QString VcsBaseDiffEditorController::startupFile() const
{
    return d->m_startupFile;
}

void VcsBaseDiffEditorController::setDisplayName(const QString &displayName)
{
    d->m_displayName = displayName;
}

void VcsBase::VcsBaseDiffEditorController::setWorkingDirectory(const FilePath &workingDir)
{
    d->m_directory = workingDir;
    setBaseDirectory(workingDir);
}

void VcsBaseDiffEditorController::setVcsTimeoutS(int value)
{
    d->m_vscTimeoutS = value;
}

void VcsBaseDiffEditorController::setVcsBinary(const FilePath &path)
{
    d->m_vcsBinary = path;
}

void VcsBaseDiffEditorController::setProcessEnvironment(const Environment &value)
{
    d->m_processEnvironment = value;
}

} // namespace VcsBase

#include "vcsbasediffeditorcontroller.moc"