forked from gdevic/GitForce
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ClassExecute.cs
231 lines (208 loc) · 7.97 KB
/
ClassExecute.cs
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
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace GitForce
{
/// <summary>
/// * Simple case: execute one command: Run()
/// returns when the command completes
/// return structure including the stdout
/// safety built-in: self-terminate if there is no response within some time
/// * More complex case: AsyncRun()
/// command takes more time: asynchronous execution with a completion callback
/// callbacks for stdout and stderr
/// can terminate execution from another thread: Terminate()
/// </summary>
public class ExecResult
{
public string stdout = string.Empty;
public string stderr = string.Empty;
public int retcode = -1;
public override string ToString()
{
return stdout;
}
public bool Success()
{
return retcode == 0;
}
}
/// <summary>
/// Contains functions to execute external console applications.
/// Standard streams (stdout/stderr) are captured and returned.
///
/// Command shell is not invoked as that would prevent capturing
/// the streams. Internally, the invocation is asynchronous.
/// </summary>
public class Exec
{
private readonly ExecResult Result = new ExecResult();
/// <summary>
/// Delegate for the completion function
/// </summary>
public delegate void PStdoutDelegate(String s);
public delegate void PStderrDelegate(String s);
public delegate void PCompleteDelegate(ExecResult result);
private Process Proc;
private Thread Thread;
private PStdoutDelegate FStdout;
private PStderrDelegate FStderr;
private PCompleteDelegate FComplete;
private Semaphore Exited = new Semaphore(0, 1);
public Exec(string cmd, string args)
{
Proc = new Process {
StartInfo =
{
FileName = cmd,
Arguments = args,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
WorkingDirectory = Directory.GetCurrentDirectory()
}};
Proc.OutputDataReceived += POutputDataReceived;
Proc.ErrorDataReceived += PErrorDataReceived;
// TODO: This is a hack for mergetool: We need to show the window to ask the user if the merge succeeded.
// The problem is with .NET (and MONO!) buffering of streams prevents us to catching the question on time.
if (args.StartsWith("mergetool "))
{
Proc.StartInfo.CreateNoWindow = false;
Proc.StartInfo.RedirectStandardOutput = false;
Proc.StartInfo.RedirectStandardError = false;
Exited = new Semaphore(0, 0);
}
// Add all environment variables registered for our process environment
foreach (var variable in ClassUtils.GetEnvars())
{
// If a variable with that name already exists, update it
if (Proc.StartInfo.EnvironmentVariables.ContainsKey(variable.Key))
Proc.StartInfo.EnvironmentVariables[variable.Key] = variable.Value;
else
Proc.StartInfo.EnvironmentVariables.Add(variable.Key, variable.Value);
}
}
/// <summary>
/// Main command execution function.
/// Upon completion, prints all errors to the log window.
/// </summary>
public static ExecResult Run(string cmd, string args)
{
App.PrintLogMessage(String.Format("Exec: {0} {1}", cmd, args));
App.StatusBusy(true);
Exec job = new Exec(cmd, args);
job.Thread = new Thread(job.ThreadedRun);
job.Thread.Start(10000);
job.Thread.Join();
// There are known problems with async output not being flushed as the
// thread exits. Releasing a time-slice using DoEvents seems to fix
// the problem in this particular setting.
Application.DoEvents();
App.StatusBusy(false);
if (job.Result.Success() == false)
App.PrintLogMessage("Error: " + job.Result.stderr);
return job.Result;
}
public void AsyncRun(PStdoutDelegate pstdout, PStderrDelegate pstderr, PCompleteDelegate pcomplete)
{
FStdout = pstdout;
FStderr = pstderr;
FComplete = pcomplete;
Thread = new Thread(ThreadedRun);
Thread.Start(0);
}
/// <summary>
/// Terminate this job
/// </summary>
public void Terminate()
{
try
{
Proc.Kill();
if (Proc.WaitForExit(10000))
Proc.WaitForExit();
}
catch (Exception)
{
App.PrintLogMessage("Exec.Terminate() exception");
}
}
/// <summary>
/// Executes a job process and blocks until it completes.
/// </summary>
private void ThreadedRun(object wait)
{
try
{
Proc.Start();
Proc.BeginOutputReadLine();
Proc.BeginErrorReadLine();
if (Proc.WaitForExit((int)wait))
Proc.WaitForExit();
// Wait for stdout and stderr signals to complete
Exited.WaitOne();
Result.retcode = Proc.ExitCode;
Proc.Close();
}
catch (Exception ex)
{
Result.stderr += ex.Message;
}
finally
{
// Call the completion function in the context of a GUI thread
if (FComplete != null)
App.MainForm.BeginInvoke((MethodInvoker) (() => FComplete(Result)));
}
}
/// <summary>
/// Callback that handles process printing to stdout.
/// Collect all strings into one stdout variable and call a custom handler.
/// </summary>
private void POutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (String.IsNullOrEmpty(e.Data)) // If the stream ended, ignore stdout
return;
if (Result.stdout != string.Empty)
Result.stdout += Environment.NewLine;
Result.stdout += e.Data;
if (FStdout != null)
App.MainForm.BeginInvoke((MethodInvoker)(() => FStdout(e.Data)));
}
/// <summary>
/// Callback that handles process printing to stderr
/// Collect all strings into one stderr variable and call a custom handler.
/// </summary>
private void PErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (String.IsNullOrEmpty(e.Data)) // If the stream ended
{
// Sometimes we receive multiple null strings on error stream
// (example: when adding a new key with plink)
// This catches these cases which would increment semaphore over it's limit
try
{
Exited.Release(); // release its semaphore
}
catch (Exception ex)
{
App.PrintLogMessage(ex.Message);
}
}
else
{
if (Result.stderr != string.Empty)
Result.stderr += Environment.NewLine;
Result.stderr += e.Data;
if (FStderr != null)
App.MainForm.BeginInvoke((MethodInvoker)(() => FStderr(e.Data)));
}
}
}
}