aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2020-06-08 16:26:59 -0700
committerRob Mensching <rob@firegiant.com>2020-06-08 16:37:14 -0700
commit02cdf55197d599d4d1fd611ad749d01f5c47a01f (patch)
tree76fcd573827fdb6b142197baa3237e6310a7bd4d /src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs
parent04b8976ca565ce95cf32a58c8725843618724383 (diff)
downloadwix-02cdf55197d599d4d1fd611ad749d01f5c47a01f.tar.gz
wix-02cdf55197d599d4d1fd611ad749d01f5c47a01f.tar.bz2
wix-02cdf55197d599d4d1fd611ad749d01f5c47a01f.zip
Add "extension" command
Diffstat (limited to 'src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs')
-rw-r--r--src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs252
1 files changed, 252 insertions, 0 deletions
diff --git a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs b/src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs
new file mode 100644
index 00000000..3ec6451e
--- /dev/null
+++ b/src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs
@@ -0,0 +1,252 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensionCache
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Threading;
10 using System.Threading.Tasks;
11 using NuGet.Common;
12 using NuGet.Configuration;
13 using NuGet.Credentials;
14 using NuGet.Packaging;
15 using NuGet.Protocol;
16 using NuGet.Protocol.Core.Types;
17 using NuGet.Versioning;
18
19 /// <summary>
20 /// Extension cache manager.
21 /// </summary>
22 public class ExtensionCacheManager
23 {
24 public string CacheFolder(bool global) => global ? this.GlobalCacheFolder() : this.LocalCacheFolder();
25
26 public string LocalCacheFolder() => Path.Combine(Environment.CurrentDirectory, @".wix\extensions\");
27
28 public string GlobalCacheFolder()
29 {
30 var baseFolder = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
31 return Path.Combine(baseFolder, @".wix\extensions\");
32 }
33
34 public async Task<bool> AddAsync(bool global, string extension, CancellationToken cancellationToken)
35 {
36 if (String.IsNullOrEmpty(extension))
37 {
38 throw new ArgumentNullException(nameof(extension));
39 }
40
41 (var extensionId, var extensionVersion) = ParseExtensionReference(extension);
42
43 var result = await this.DownloadAndExtractAsync(global, extensionId, extensionVersion, cancellationToken);
44
45 return result;
46 }
47
48 public Task<bool> RemoveAsync(bool global, string extension, CancellationToken cancellationToken)
49 {
50 if (String.IsNullOrEmpty(extension))
51 {
52 throw new ArgumentNullException(nameof(extension));
53 }
54
55 (var extensionId, var extensionVersion) = ParseExtensionReference(extension);
56
57 var cacheFolder = this.CacheFolder(global);
58
59 cacheFolder = Path.Combine(cacheFolder, extensionId, extensionVersion);
60
61 if (Directory.Exists(cacheFolder))
62 {
63 cancellationToken.ThrowIfCancellationRequested();
64
65 Directory.Delete(cacheFolder, true);
66 return Task.FromResult(true);
67 }
68
69 return Task.FromResult(false);
70 }
71
72 public Task<IEnumerable<CachedExtension>> ListAsync(bool global, string extension, CancellationToken cancellationToken)
73 {
74 var found = new List<CachedExtension>();
75
76 (var extensionId, var extensionVersion) = ParseExtensionReference(extension);
77
78 var cacheFolder = this.CacheFolder(global);
79
80 var searchFolder = Path.Combine(cacheFolder, extensionId, extensionVersion);
81
82 if (!Directory.Exists(searchFolder))
83 {
84 }
85 else if (!String.IsNullOrEmpty(extensionVersion)) // looking for an explicit version of an extension.
86 {
87 var extensionFolder = Path.Combine(cacheFolder, extensionId, extensionVersion);
88 if (Directory.Exists(extensionFolder))
89 {
90 var present = ExtensionFileExists(cacheFolder, extensionId, extensionVersion);
91 found.Add(new CachedExtension(extensionId, extensionVersion, !present));
92 }
93 }
94 else // looking for all versions of an extension or all versions of all extensions.
95 {
96 IEnumerable<string> foundExtensionIds;
97
98 if (String.IsNullOrEmpty(extensionId))
99 {
100 // Looking for all versions of all extensions.
101 foundExtensionIds = Directory.GetDirectories(cacheFolder).Select(folder => Path.GetFileName(folder)).ToList();
102 }
103 else
104 {
105 // Looking for all versions of a single extension.
106 var extensionFolder = Path.Combine(cacheFolder, extensionId);
107 foundExtensionIds = Directory.Exists(extensionFolder) ? new[] { extensionId } : Array.Empty<string>();
108 }
109
110 foreach (var foundExtensionId in foundExtensionIds)
111 {
112 var extensionFolder = Path.Combine(cacheFolder, foundExtensionId);
113
114 foreach (var folder in Directory.GetDirectories(extensionFolder))
115 {
116 cancellationToken.ThrowIfCancellationRequested();
117
118 var foundExtensionVersion = Path.GetFileName(folder);
119
120 if (!NuGetVersion.TryParse(foundExtensionVersion, out _))
121 {
122 continue;
123 }
124
125 var present = ExtensionFileExists(cacheFolder, foundExtensionId, foundExtensionVersion);
126 found.Add(new CachedExtension(foundExtensionId, foundExtensionVersion, !present));
127 }
128 }
129 }
130
131 return Task.FromResult((IEnumerable<CachedExtension>)found);
132 }
133
134 private async Task<bool> DownloadAndExtractAsync(bool global, string id, string version, CancellationToken cancellationToken)
135 {
136 var logger = NullLogger.Instance;
137
138 DefaultCredentialServiceUtility.SetupDefaultCredentialService(logger, nonInteractive: false);
139
140 var settings = Settings.LoadDefaultSettings(root: Environment.CurrentDirectory);
141 var sources = PackageSourceProvider.LoadPackageSources(settings).Where(s => s.IsEnabled);
142
143 using (var cache = new SourceCacheContext())
144 {
145 PackageSource versionSource = null;
146
147 var nugetVersion = String.IsNullOrEmpty(version) ? null : new NuGetVersion(version);
148
149 if (nugetVersion is null)
150 {
151 foreach (var source in sources)
152 {
153 var repository = Repository.Factory.GetCoreV3(source.Source);
154 var resource = await repository.GetResourceAsync<FindPackageByIdResource>();
155
156 var availableVersions = await resource.GetAllVersionsAsync(id, cache, logger, cancellationToken);
157 foreach (var availableVersion in availableVersions)
158 {
159 if (nugetVersion is null || nugetVersion < availableVersion)
160 {
161 nugetVersion = availableVersion;
162 versionSource = source;
163 }
164 }
165 }
166
167 if (nugetVersion is null)
168 {
169 return false;
170 }
171 }
172
173 var searchSources = versionSource is null ? sources : new[] { versionSource };
174
175 var extensionFolder = Path.Combine(this.CacheFolder(global), id, nugetVersion.ToString());
176
177 foreach (var source in searchSources)
178 {
179 var repository = Repository.Factory.GetCoreV3(source.Source);
180 var resource = await repository.GetResourceAsync<FindPackageByIdResource>();
181
182 using (var stream = new MemoryStream())
183 {
184 var downloaded = await resource.CopyNupkgToStreamAsync(id, nugetVersion, stream, cache, logger, cancellationToken);
185
186 if (downloaded)
187 {
188 stream.Position = 0;
189
190 using (var archive = new PackageArchiveReader(stream))
191 {
192 var files = PackagingConstants.Folders.Known.SelectMany(folder => archive.GetFiles(folder)).Distinct(StringComparer.OrdinalIgnoreCase);
193 await archive.CopyFilesAsync(extensionFolder, files, this.ExtractProgress, logger, cancellationToken);
194 }
195
196 return true;
197 }
198 }
199 }
200 }
201
202 return false;
203 }
204
205 private string ExtractProgress(string sourceFile, string targetPath, Stream fileStream) => fileStream.CopyToFile(targetPath);
206
207 private static (string extensionId, string extensionVersion) ParseExtensionReference(string extensionReference)
208 {
209 var extensionId = extensionReference ?? String.Empty;
210 var extensionVersion = String.Empty;
211
212 var index = extensionId.LastIndexOf('/');
213 if (index > 0)
214 {
215 extensionVersion = extensionReference.Substring(index + 1);
216 extensionId = extensionReference.Substring(0, index);
217
218 if (!NuGetVersion.TryParse(extensionVersion, out _))
219 {
220 throw new ArgumentException($"Invalid extension version in {extensionReference}");
221 }
222
223 if (String.IsNullOrEmpty(extensionId))
224 {
225 throw new ArgumentException($"Invalid extension id in {extensionReference}");
226 }
227 }
228
229 return (extensionId, extensionVersion);
230 }
231
232 private static bool ExtensionFileExists(string baseFolder, string extensionId, string extensionVersion)
233 {
234 var toolsFolder = Path.Combine(baseFolder, extensionId, extensionVersion, "tools");
235 if (!Directory.Exists(toolsFolder))
236 {
237 return false;
238 }
239
240 var extensionAssembly = Path.Combine(toolsFolder, extensionId + ".dll");
241
242 var present = File.Exists(extensionAssembly);
243 if (!present)
244 {
245 extensionAssembly = Path.Combine(toolsFolder, extensionId + ".exe");
246 present = File.Exists(extensionAssembly);
247 }
248
249 return present;
250 }
251 }
252}