main
cs 305 lines 9.92 KB
Raw
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
3 namespace WixToolset.Data
4 {
5 using System;
6 using System.IO;
7 using System.IO.Compression;
8 using System.Reflection;
9 using System.Text;
10
11 /// <summary>
12 /// Class that understands the standard file structure of the WiX toolset.
13 /// </summary>
14 public class WixOutput : IDisposable
15 {
16 private readonly Stream stream;
17 private ZipArchive archive;
18 private bool disposed;
19
20 private WixOutput(Uri uri, ZipArchive archive, Stream stream)
21 {
22 this.Uri = uri;
23 this.archive = archive;
24 this.stream = stream;
25 }
26
27 /// <summary>
28 ///
29 /// </summary>
30 public Uri Uri { get; }
31
32 /// <summary>
33 /// Creates a new file structure in memory.
34 /// </summary>
35 /// <returns>Newly created <c>WixOutput</c>.</returns>
36 public static WixOutput Create()
37 {
38 var uri = new Uri("memorystream:");
39
40 var stream = new MemoryStream();
41
42 return WixOutput.Create(uri, stream);
43 }
44
45 /// <summary>
46 /// Creates a new file structure on disk.
47 /// </summary>
48 /// <param name="path">Path to write file structure to.</param>
49 /// <returns>Newly created <c>WixOutput</c>.</returns>
50 public static WixOutput Create(string path)
51 {
52 var fullPath = Path.GetFullPath(path);
53
54 Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
55
56 var uri = new Uri(fullPath);
57
58 var stream = File.Create(path);
59
60 return WixOutput.Create(uri, stream);
61 }
62
63 /// <summary>
64 /// Creates a new file structure.
65 /// </summary>
66 /// <param name="uri"></param>
67 /// <param name="stream">Stream to write the file structure to.</param>
68 /// <returns>Newly created <c>WixOutput</c>.</returns>
69 public static WixOutput Create(Uri uri, Stream stream)
70 {
71 var archive = new ZipArchive(stream, ZipArchiveMode.Update, leaveOpen: true);
72
73 return new WixOutput(uri, archive, stream);
74 }
75
76 /// <summary>
77 /// Loads a wixout from a path on disk.
78 /// </summary>
79 /// <param name="path">Path to wixout file saved on disk.</param>
80 /// <returns>Loaded created <c>WixOutput</c>.</returns>
81 public static WixOutput Read(string path)
82 {
83 var uri = new Uri(Path.GetFullPath(path));
84
85 var stream = File.OpenRead(path);
86
87 return Read(uri, stream);
88 }
89
90 /// <summary>
91 /// Loads a wixout from a path on disk or embedded resource in assembly.
92 /// </summary>
93 /// <param name="baseUri">Uri with local path to wixout file saved on disk or embedded resource in assembly.</param>
94 /// <returns>Loaded created <c>WixOutput</c>.</returns>
95 public static WixOutput Read(Uri baseUri)
96 {
97 // If the embedded files are stored in an assembly resource stream (usually
98 // a .wixlib embedded in a WixExtension).
99 if ("embeddedresource" == baseUri.Scheme)
100 {
101 var assemblyPath = Path.GetFullPath(baseUri.LocalPath);
102 var resourceName = baseUri.Fragment.TrimStart('#');
103
104 var assembly = Assembly.LoadFile(assemblyPath);
105 return WixOutput.Read(assembly, resourceName);
106 }
107 else // normal file (usually a binary .wixlib on disk).
108 {
109 var stream = File.OpenRead(baseUri.LocalPath);
110 return WixOutput.Read(baseUri, stream);
111 }
112 }
113
114 /// <summary>
115 /// Loads a wixout from an assembly resource stream.
116 /// </summary>
117 /// <param name="assembly"></param>
118 /// <param name="resourceName"></param>
119 /// <returns>Loaded created <c>WixOutput</c>.</returns>
120 public static WixOutput Read(Assembly assembly, string resourceName)
121 {
122 var resourceStream = assembly.GetManifestResourceStream(resourceName);
123
124 var uriBuilder = new UriBuilder(assembly.CodeBase)
125 {
126 Scheme = "embeddedresource",
127 Fragment = resourceName
128 };
129
130 return Read(uriBuilder.Uri, resourceStream);
131 }
132
133 /// <summary>
134 /// Reads a file structure from an open stream.
135 /// </summary>
136 /// <param name="uri"></param>
137 /// <param name="stream">Stream to read from.</param>
138 /// <returns>Loaded created <c>WixOutput</c>.</returns>
139 public static WixOutput Read(Uri uri, Stream stream)
140 {
141 try
142 {
143 var archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: true);
144
145 return new WixOutput(uri, archive, stream);
146 }
147 catch (InvalidDataException)
148 {
149 throw new WixException(ErrorMessages.CorruptFileFormat(uri.AbsoluteUri, "wixout"));
150 }
151 }
152
153 /// <summary>
154 /// Reopen the underlying archive for read-only or read-write access.
155 /// </summary>
156 /// <param name="writable">Indicates whether the output can be modified. Defaults to false.</param>
157 public void Reopen(bool writable = false)
158 {
159 this.archive?.Dispose();
160 this.archive = null;
161
162 this.archive = new ZipArchive(this.stream, writable ? ZipArchiveMode.Update : ZipArchiveMode.Read, leaveOpen: true);
163 }
164
165 /// <summary>
166 /// Extracts an embedded file.
167 /// </summary>
168 /// <param name="embeddedId">Id to the file to extract.</param>
169 /// <param name="outputPath">Path to write the extracted file to.</param>
170 public void ExtractEmbeddedFile(string embeddedId, string outputPath)
171 {
172 var entry = this.archive.GetEntry(embeddedId);
173
174 if (entry == null)
175 {
176 throw new ArgumentOutOfRangeException(nameof(embeddedId));
177 }
178
179 var folder = Path.GetDirectoryName(outputPath);
180
181 Directory.CreateDirectory(folder);
182
183 entry.ExtractToFile(outputPath, overwrite: true);
184 }
185
186 /// <summary>
187 /// Creates a data stream in the wixout.
188 /// </summary>
189 /// <returns>Stream to the data of the file.</returns>
190 public Stream CreateDataStream(string name)
191 {
192 if (this.archive.Mode == ZipArchiveMode.Update)
193 {
194 this.DeleteExistingEntry(name);
195 }
196
197 var entry = this.archive.CreateEntry(name);
198
199 return entry.Open();
200 }
201
202 /// <summary>
203 /// Imports a file from disk into the output.
204 /// </summary>
205 /// <param name="name">Name of the stream in the output.</param>
206 /// <param name="path">Path to file on disk to include in the output.</param>
207 public void ImportDataStream(string name, string path)
208 {
209 if (this.archive.Mode == ZipArchiveMode.Update)
210 {
211 this.DeleteExistingEntry(name);
212 }
213
214 this.archive.CreateEntryFromFile(path, name, System.IO.Compression.CompressionLevel.Optimal);
215 }
216
217 /// <summary>
218 /// Gets a non-closing stream to the data of the file.
219 /// </summary>
220 /// <returns>Stream to the data of the file.</returns>
221 public Stream GetDataStream(string name)
222 {
223 var entry = this.archive.GetEntry(name);
224
225 if (entry == null)
226 {
227 throw new ArgumentOutOfRangeException(nameof(name));
228 }
229
230 return entry.Open();
231 }
232
233 /// <summary>
234 /// Gets the data of the file as a string.
235 /// </summary>
236 /// <returns>String contents data of the file.</returns>
237 public string GetData(string name)
238 {
239 var entry = this.archive.GetEntry(name);
240
241 // Use StreamReader to "swallow" BOM if present.
242 using (var stream = entry.Open())
243 using (var streamReader = new StreamReader(stream, Encoding.UTF8))
244 {
245 return streamReader.ReadToEnd();
246 }
247 }
248
249 /// <summary>
250 /// Creates a new file structure on disk that can only be written to.
251 /// </summary>
252 /// <param name="path">Path to write file structure to.</param>
253 /// <returns>Newly created <c>WixOutput</c>.</returns>
254 internal static WixOutput CreateNew(string path)
255 {
256 var fullPath = Path.GetFullPath(path);
257
258 Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
259
260 var uri = new Uri(fullPath);
261
262 var stream = File.Create(path);
263
264 var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true);
265
266 return new WixOutput(uri, archive, stream);
267 }
268
269 /// <summary>
270 /// Disposes of the internal state of the file structure.
271 /// </summary>
272 public void Dispose()
273 {
274 this.Dispose(true);
275 GC.SuppressFinalize(this);
276 }
277
278 /// <summary>
279 /// Disposes of the internsl state of the file structure.
280 /// </summary>
281 /// <param name="disposing">True if disposing.</param>
282 protected virtual void Dispose(bool disposing)
283 {
284 if (!this.disposed)
285 {
286 if (disposing)
287 {
288 this.archive?.Dispose();
289 this.stream?.Dispose();
290 }
291 }
292
293 this.disposed = true;
294 }
295
296 private void DeleteExistingEntry(string name)
297 {
298 var entry = this.archive.GetEntry(name);
299 if (entry != null)
300 {
301 entry.Delete();
302 }
303 }
304 }
305 }