A game about forced loneliness, made by TACStudios
1/*---------------------------------------------------------------------------------------------
2 * Copyright (c) Microsoft Corporation. All rights reserved.
3 * Licensed under the MIT License. See License.txt in the project root for license information.
4 *--------------------------------------------------------------------------------------------*/
5
6using System;
7using System.Diagnostics;
8using System.Text;
9using System.Threading;
10using System.Threading.Tasks;
11
12namespace Microsoft.Unity.VisualStudio.Editor
13{
14 internal class ProcessRunnerResult
15 {
16 public bool Success { get; set; }
17 public string Output { get; set; }
18 public string Error { get; set; }
19 }
20
21 internal static class ProcessRunner
22 {
23 public const int DefaultTimeoutInMilliseconds = 300000;
24
25 public static ProcessStartInfo ProcessStartInfoFor(string filename, string arguments, bool redirect = true, bool shell = false)
26 {
27 return new ProcessStartInfo
28 {
29 UseShellExecute = shell,
30 CreateNoWindow = true,
31 RedirectStandardOutput = redirect,
32 RedirectStandardError = redirect,
33 FileName = filename,
34 Arguments = arguments
35 };
36 }
37
38 public static void Start(string filename, string arguments)
39 {
40 Start(ProcessStartInfoFor(filename, arguments, false));
41 }
42
43 public static void Start(ProcessStartInfo processStartInfo)
44 {
45 var process = new Process { StartInfo = processStartInfo };
46
47 using (process)
48 {
49 process.Start();
50 }
51 }
52
53 public static ProcessRunnerResult StartAndWaitForExit(string filename, string arguments, int timeoutms = DefaultTimeoutInMilliseconds, Action<string> onOutputReceived = null)
54 {
55 return StartAndWaitForExit(ProcessStartInfoFor(filename, arguments), timeoutms, onOutputReceived);
56 }
57
58 public static ProcessRunnerResult StartAndWaitForExit(ProcessStartInfo processStartInfo, int timeoutms = DefaultTimeoutInMilliseconds, Action<string> onOutputReceived = null)
59 {
60 var process = new Process { StartInfo = processStartInfo };
61
62 using (process)
63 {
64 var sbOutput = new StringBuilder();
65 var sbError = new StringBuilder();
66
67 var outputSource = new TaskCompletionSource<bool>();
68 var errorSource = new TaskCompletionSource<bool>();
69
70 process.OutputDataReceived += (_, e) =>
71 {
72 Append(sbOutput, e.Data, outputSource);
73 if (onOutputReceived != null && e.Data != null)
74 onOutputReceived(e.Data);
75 };
76 process.ErrorDataReceived += (_, e) => Append(sbError, e.Data, errorSource);
77
78 process.Start();
79 process.BeginOutputReadLine();
80 process.BeginErrorReadLine();
81
82 var run = Task.Run(() => process.WaitForExit(timeoutms));
83 var processTask = Task.WhenAll(run, outputSource.Task, errorSource.Task);
84
85 if (Task.WhenAny(Task.Delay(timeoutms), processTask).Result == processTask && run.Result)
86 return new ProcessRunnerResult {Success = true, Error = sbError.ToString(), Output = sbOutput.ToString()};
87
88 try
89 {
90 process.Kill();
91 }
92 catch
93 {
94 /* ignore */
95 }
96
97 return new ProcessRunnerResult {Success = false, Error = sbError.ToString(), Output = sbOutput.ToString()};
98 }
99 }
100
101 private static void Append(StringBuilder sb, string data, TaskCompletionSource<bool> taskSource)
102 {
103 if (data == null)
104 {
105 taskSource.SetResult(true);
106 return;
107 }
108
109 sb?.Append(data);
110 }
111 }
112}