PageRenderTime 62ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 0ms

/GitUI/BuildServerIntegration/BuildServerWatcher.cs

https://github.com/vbjay/gitextensions
C# | 331 lines | 276 code | 53 blank | 2 comment | 44 complexity | cc5ad90584c9f61d7c57e6c34ac00810 MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.0
  1. using System;
  2. using System.IO;
  3. using System.IO.IsolatedStorage;
  4. using System.Linq;
  5. using System.Reactive.Concurrency;
  6. using System.Reactive.Disposables;
  7. using System.Reactive.Linq;
  8. using System.Security.Cryptography;
  9. using System.Text;
  10. using System.Threading;
  11. using System.Windows.Forms;
  12. using GitCommands;
  13. using GitCommands.Config;
  14. using GitUI.HelperDialogs;
  15. using GitUI.RevisionGridClasses;
  16. using GitUIPluginInterfaces;
  17. using GitUIPluginInterfaces.BuildServerIntegration;
  18. namespace GitUI.BuildServerIntegration
  19. {
  20. public class BuildServerWatcher : IBuildServerWatcher, IDisposable
  21. {
  22. private readonly RevisionGrid revisionGrid;
  23. private readonly DvcsGraph revisions;
  24. private GitModule Module { get { return revisionGrid.Module; } }
  25. public int BuildStatusImageColumnIndex { get; private set; }
  26. public int BuildStatusMessageColumnIndex { get; private set; }
  27. private IDisposable buildStatusCancellationToken;
  28. private IBuildServerAdapter buildServerAdapter;
  29. private readonly object buildServerCredentialsLock = new object();
  30. public BuildServerWatcher(RevisionGrid revisionGrid, DvcsGraph revisions)
  31. {
  32. this.revisionGrid = revisionGrid;
  33. this.revisions = revisions;
  34. BuildStatusImageColumnIndex = -1;
  35. BuildStatusMessageColumnIndex = -1;
  36. }
  37. public void LaunchBuildServerInfoFetchOperation()
  38. {
  39. CancelBuildStatusFetchOperation();
  40. DisposeBuildServerAdapter();
  41. // Extract the project name from the last part of the directory path. It is assumed that it matches the project name in the CI build server.
  42. buildServerAdapter = GetBuildServerAdapter();
  43. UpdateUI();
  44. if (buildServerAdapter == null)
  45. return;
  46. var scheduler = NewThreadScheduler.Default;
  47. var fullDayObservable = buildServerAdapter.GetFinishedBuildsSince(scheduler, DateTime.Today - TimeSpan.FromDays(3));
  48. var fullObservable = buildServerAdapter.GetFinishedBuildsSince(scheduler);
  49. var fromNowObservable = buildServerAdapter.GetFinishedBuildsSince(scheduler, DateTime.Now);
  50. var runningBuildsObservable = buildServerAdapter.GetRunningBuilds(scheduler);
  51. var cancellationToken = new CompositeDisposable
  52. {
  53. fullDayObservable.OnErrorResumeNext(fullObservable)
  54. .OnErrorResumeNext(Observable.Empty<BuildInfo>()
  55. .DelaySubscription(TimeSpan.FromMinutes(1))
  56. .OnErrorResumeNext(fromNowObservable)
  57. .Retry()
  58. .Repeat())
  59. .ObserveOn(SynchronizationContext.Current)
  60. .Subscribe(OnBuildInfoUpdate),
  61. runningBuildsObservable.OnErrorResumeNext(Observable.Empty<BuildInfo>()
  62. .DelaySubscription(TimeSpan.FromSeconds(10)))
  63. .Retry()
  64. .Repeat()
  65. .ObserveOn(SynchronizationContext.Current)
  66. .Subscribe(OnBuildInfoUpdate)
  67. };
  68. buildStatusCancellationToken = cancellationToken;
  69. }
  70. public void CancelBuildStatusFetchOperation()
  71. {
  72. var cancellationToken = Interlocked.Exchange(ref buildStatusCancellationToken, null);
  73. if (cancellationToken != null)
  74. {
  75. cancellationToken.Dispose();
  76. }
  77. }
  78. public IBuildServerCredentials GetBuildServerCredentials(IBuildServerAdapter buildServerAdapter, bool useStoredCredentialsIfExisting)
  79. {
  80. lock (buildServerCredentialsLock)
  81. {
  82. IBuildServerCredentials buildServerCredentials = new BuildServerCredentials { UseGuestAccess = true };
  83. const string CredentialsConfigName = "Credentials";
  84. const string UseGuestAccessKey = "UseGuestAccess";
  85. const string UsernameKey = "Username";
  86. const string PasswordKey = "Password";
  87. using (var stream = GetBuildServerOptionsIsolatedStorageStream(buildServerAdapter, FileAccess.Read, FileShare.Read))
  88. {
  89. if (stream.Position < stream.Length)
  90. {
  91. var protectedData = new byte[stream.Length];
  92. stream.Read(protectedData, 0, (int)stream.Length);
  93. byte[] unprotectedData = ProtectedData.Unprotect(protectedData, null, DataProtectionScope.CurrentUser);
  94. using (var memoryStream = new MemoryStream(unprotectedData))
  95. {
  96. ConfigFile credentialsConfig = new ConfigFile("", false);
  97. using (var textReader = new StreamReader(memoryStream, Encoding.UTF8))
  98. {
  99. credentialsConfig.LoadFromString(textReader.ReadToEnd());
  100. }
  101. ConfigSection section = credentialsConfig.FindConfigSection(CredentialsConfigName);
  102. if (section != null)
  103. {
  104. buildServerCredentials.UseGuestAccess = section.GetValueAsBool(UseGuestAccessKey, true);
  105. buildServerCredentials.Username = section.GetValue(UsernameKey);
  106. buildServerCredentials.Password = section.GetValue(PasswordKey);
  107. if (useStoredCredentialsIfExisting)
  108. {
  109. return buildServerCredentials;
  110. }
  111. }
  112. }
  113. }
  114. }
  115. if (!useStoredCredentialsIfExisting)
  116. {
  117. buildServerCredentials = ShowBuildServerCredentialsForm(buildServerAdapter.UniqueKey, buildServerCredentials);
  118. if (buildServerCredentials != null)
  119. {
  120. ConfigFile credentialsConfig = new ConfigFile("", true);
  121. ConfigSection section = credentialsConfig.FindOrCreateConfigSection(CredentialsConfigName);
  122. section.SetValueAsBool(UseGuestAccessKey, buildServerCredentials.UseGuestAccess);
  123. section.SetValue(UsernameKey, buildServerCredentials.Username);
  124. section.SetValue(PasswordKey, buildServerCredentials.Password);
  125. using (var stream = GetBuildServerOptionsIsolatedStorageStream(buildServerAdapter, FileAccess.Write, FileShare.None))
  126. {
  127. using (var memoryStream = new MemoryStream())
  128. {
  129. using (var textWriter = new StreamWriter(memoryStream, Encoding.UTF8))
  130. {
  131. textWriter.Write(credentialsConfig.GetAsString());
  132. }
  133. var protectedData = ProtectedData.Protect(memoryStream.ToArray(), null, DataProtectionScope.CurrentUser);
  134. stream.Write(protectedData, 0, protectedData.Length);
  135. }
  136. }
  137. return buildServerCredentials;
  138. }
  139. }
  140. return null;
  141. }
  142. }
  143. private IBuildServerCredentials ShowBuildServerCredentialsForm(string buildServerUniqueKey, IBuildServerCredentials buildServerCredentials)
  144. {
  145. if (revisionGrid.InvokeRequired)
  146. {
  147. return (IBuildServerCredentials)revisionGrid.Invoke(new Func<IBuildServerCredentials>(() => ShowBuildServerCredentialsForm(buildServerUniqueKey, buildServerCredentials)));
  148. }
  149. using (var form = new FormBuildServerCredentials(buildServerUniqueKey))
  150. {
  151. form.BuildServerCredentials = buildServerCredentials;
  152. if (form.ShowDialog(revisionGrid) == DialogResult.OK)
  153. {
  154. return buildServerCredentials;
  155. }
  156. }
  157. return null;
  158. }
  159. private void AddBuildStatusColumns()
  160. {
  161. if (BuildStatusImageColumnIndex == -1)
  162. {
  163. var buildStatusImageColumn = new DataGridViewImageColumn
  164. {
  165. AutoSizeMode = DataGridViewAutoSizeColumnMode.None,
  166. Width = 16,
  167. ReadOnly = true,
  168. SortMode = DataGridViewColumnSortMode.NotSortable
  169. };
  170. BuildStatusImageColumnIndex = revisions.Columns.Add(buildStatusImageColumn);
  171. }
  172. if (BuildStatusMessageColumnIndex == -1 && Module.Settings.BuildServer.ShowBuildSummaryInGrid.ValueOrDefault)
  173. {
  174. var buildMessageTextBoxColumn = new DataGridViewTextBoxColumn
  175. {
  176. AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
  177. ReadOnly = true,
  178. SortMode = DataGridViewColumnSortMode.NotSortable
  179. };
  180. BuildStatusMessageColumnIndex = revisions.Columns.Add(buildMessageTextBoxColumn);
  181. }
  182. }
  183. private void OnBuildInfoUpdate(BuildInfo buildInfo)
  184. {
  185. if (buildStatusCancellationToken == null)
  186. return;
  187. foreach (var commitHash in buildInfo.CommitHashList)
  188. {
  189. string graphRevision;
  190. int row = revisionGrid.TrySearchRevision(commitHash, out graphRevision);
  191. if (row >= 0)
  192. {
  193. var rowData = revisions.GetRowData(row);
  194. if (rowData.BuildStatus == null ||
  195. buildInfo.StartDate >= rowData.BuildStatus.StartDate)
  196. {
  197. rowData.BuildStatus = buildInfo;
  198. if (BuildStatusImageColumnIndex != -1)
  199. revisions.UpdateCellValue(BuildStatusImageColumnIndex, row);
  200. if (BuildStatusMessageColumnIndex != -1)
  201. revisions.UpdateCellValue(BuildStatusMessageColumnIndex, row);
  202. }
  203. }
  204. }
  205. }
  206. private IBuildServerAdapter GetBuildServerAdapter()
  207. {
  208. if (Module.Settings.BuildServer.EnableIntegration.ValueOrDefault)
  209. {
  210. var buildServerType = Module.Settings.BuildServer.Type.Value;
  211. if (!string.IsNullOrEmpty(buildServerType))
  212. {
  213. var exports = ManagedExtensibility.CompositionContainer.GetExports<IBuildServerAdapter, IBuildServerTypeMetadata>();
  214. var export = exports.SingleOrDefault(x => x.Metadata.BuildServerType == buildServerType);
  215. if (export != null)
  216. {
  217. try
  218. {
  219. var canBeLoaded = export.Metadata.CanBeLoaded;
  220. if (!canBeLoaded.IsNullOrEmpty())
  221. {
  222. System.Diagnostics.Debug.Write(export.Metadata.BuildServerType + " adapter could not be loaded: " + canBeLoaded);
  223. return null;
  224. }
  225. var buildServerAdapter = export.Value;
  226. buildServerAdapter.Initialize(this, Module.Settings.BuildServer.TypeSettings);
  227. return buildServerAdapter;
  228. }
  229. catch (InvalidOperationException ex)
  230. {
  231. System.Diagnostics.Debug.Write(ex);
  232. // Invalid arguments, do not return a build server adapter
  233. }
  234. }
  235. }
  236. }
  237. return null;
  238. }
  239. private void UpdateUI()
  240. {
  241. var columnsAreVisible = buildServerAdapter != null;
  242. if (columnsAreVisible)
  243. {
  244. AddBuildStatusColumns();
  245. }
  246. if (BuildStatusImageColumnIndex != -1)
  247. revisions.Columns[BuildStatusImageColumnIndex].Visible = columnsAreVisible;
  248. if (BuildStatusMessageColumnIndex != -1)
  249. revisions.Columns[BuildStatusMessageColumnIndex].Visible = columnsAreVisible && Module.Settings.BuildServer.ShowBuildSummaryInGrid.ValueOrDefault;
  250. }
  251. public void Dispose()
  252. {
  253. Dispose(true);
  254. GC.SuppressFinalize(this);
  255. }
  256. private void Dispose(bool disposing)
  257. {
  258. if (disposing)
  259. {
  260. CancelBuildStatusFetchOperation();
  261. DisposeBuildServerAdapter();
  262. }
  263. }
  264. private void DisposeBuildServerAdapter()
  265. {
  266. if (buildServerAdapter != null)
  267. {
  268. buildServerAdapter.Dispose();
  269. buildServerAdapter = null;
  270. }
  271. }
  272. private static IsolatedStorageFileStream GetBuildServerOptionsIsolatedStorageStream(IBuildServerAdapter buildServerAdapter, FileAccess fileAccess, FileShare fileShare)
  273. {
  274. var fileName = string.Format("BuildServer-{0}.options", Convert.ToBase64String(Encoding.UTF8.GetBytes(buildServerAdapter.UniqueKey)));
  275. return new IsolatedStorageFileStream(fileName, FileMode.OpenOrCreate, fileAccess, fileShare);
  276. }
  277. }
  278. }