-
Notifications
You must be signed in to change notification settings - Fork 414
Expand file tree
/
Copy pathDependencyNugetUpdateBlock.cs
More file actions
132 lines (105 loc) · 5.27 KB
/
DependencyNugetUpdateBlock.cs
File metadata and controls
132 lines (105 loc) · 5.27 KB
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
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.
using System.Text.RegularExpressions;
using System.Threading.Tasks.Dataflow;
using System.Timers;
using Microsoft.VisualStudio.Collections;
using Microsoft.VisualStudio.ProjectSystem.Tree.Dependencies.Models;
namespace Microsoft.VisualStudio.ProjectSystem.Tree.Dependencies;
[Export(typeof(IDependencyNugetUpdateBlock))]
internal class DependencyNugetUpdateBlock: ProjectValueDataSourceBase<Dictionary<string, DiagnosticLevel>>, IDependencyNugetUpdateBlock
{
private int _sourceVersion;
private IBroadcastBlock<IProjectVersionedValue<Dictionary<string, DiagnosticLevel>>> _broadcastBlock = null!;
private IReceivableSourceBlock<IProjectVersionedValue<Dictionary<string, DiagnosticLevel>>> _publicBlock = null!;
private Dictionary<string, DiagnosticLevel>? _lastPublishedValue;
public override NamedIdentity DataSourceKey { get; } = new(nameof(DependencyNugetUpdateBlock));
public override IComparable DataSourceVersion => _sourceVersion;
[ImportingConstructor]
public DependencyNugetUpdateBlock(UnconfiguredProject unconfiguredProject)
: base(unconfiguredProject.Services, synchronousDisposal: false, registerDataSource: false)
{
}
public override IReceivableSourceBlock<IProjectVersionedValue<Dictionary<string, DiagnosticLevel>>> SourceBlock
{
get
{
EnsureInitialized();
return _publicBlock;
}
}
protected override void Initialize()
{
#pragma warning disable RS0030
base.Initialize();
#pragma warning restore RS0030
_broadcastBlock = DataflowBlockSlim.CreateBroadcastBlock<IProjectVersionedValue<Dictionary<string, DiagnosticLevel>>>(nameFormat: $"{nameof(DependencyNugetUpdateBlock)} {1}");
_publicBlock = _broadcastBlock.SafePublicize();
PostNewValue(GetNewValue()); // TODO currently blocks receiving dependency model to make initial request
var timer = new System.Timers.Timer();
timer.Elapsed += OnRefreshDependencyStatus;
timer.Interval = TimeSpan.FromMinutes(15).TotalMilliseconds;
timer.Start();
}
private void OnRefreshDependencyStatus(object sender, ElapsedEventArgs elapsedEventArgs)
{
PostNewValue(GetNewValue());
}
private string RunCommandSynchronouslyAndReceiveOutput(string command)
{
var process = new System.Diagnostics.Process();
var startInfo = new System.Diagnostics.ProcessStartInfo
{
WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden,
FileName = "cmd.exe",
Arguments = $"/C {command}",
RedirectStandardOutput = true,
UseShellExecute = false
};
process.StartInfo = startInfo;
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return output;
}
private Dictionary<string, DiagnosticLevel> GetNewValue()
{
Dictionary<string, DiagnosticLevel> packageDiagnosticLevels = new();
string dotnetListVulnerableCommandOutput = RunCommandSynchronouslyAndReceiveOutput("dotnet list package --vulnerable");
string dotnetListOutdatedCommandOutput = RunCommandSynchronouslyAndReceiveOutput("dotnet list package --outdated");
string dotnetListDeprecatedCommandOutput = RunCommandSynchronouslyAndReceiveOutput("dotnet list package --deprecated");
foreach (Match match in Regex.Matches(dotnetListVulnerableCommandOutput, "> ([^\\s]+)\\s+"))
{
AddPackageIfLevelHasPriority(match.Groups[1].Value, DiagnosticLevel.Vulnerability);
}
foreach (Match match in Regex.Matches(dotnetListOutdatedCommandOutput, "> ([^\\s]+)\\s+"))
{
AddPackageIfLevelHasPriority(match.Groups[1].Value, DiagnosticLevel.UpgradeAvailable);
}
foreach (Match match in Regex.Matches(dotnetListDeprecatedCommandOutput, "> ([^\\s]+)\\s+"))
{
AddPackageIfLevelHasPriority(match.Groups[1].Value, DiagnosticLevel.Deprecation);
}
void AddPackageIfLevelHasPriority(string package, DiagnosticLevel level)
{
if (!packageDiagnosticLevels.TryGetValue(package, out DiagnosticLevel existingValue) || existingValue < level)
{
packageDiagnosticLevels[package] = level;
}
}
return packageDiagnosticLevels;
}
private void PostNewValue(Dictionary<string, DiagnosticLevel> newValue)
{
// Add thread safety as needed. Make sure to never regress the data source version published
if (!DictionaryEqualityComparer<string, DiagnosticLevel>.Instance.Equals(newValue, _lastPublishedValue)) // only publish if you have to
{
_lastPublishedValue = newValue;
_broadcastBlock.Post(
new ProjectVersionedValue<Dictionary<string, DiagnosticLevel>>(
newValue,
ImmutableDictionary.Create<NamedIdentity, IComparable>().Add(
DataSourceKey,
_sourceVersion++)));
}
}
}