@joebigelow / wix-1 / commits / 6c1ae259

Implement WixOutput with support for multiple streams of data

Rob Mensching committed Oct 25, 2019 at 00:23 UTC 6c1ae2593faab59e1a01c96794e0835a6fcd0626
21 files changed +399 -846
src/WixToolset.Data/ErrorMessages.cs
+4 -4
@@ -198,9 +198,9 @@ namespace WixToolset.Data
198 return Message(sourceLineNumbers, Ids.ConditionExpected, "The {0} element's inner text cannot be an empty string or completely whitespace. If you don't want a condition, then simply remove the entire {0} element.", elementName);
199 }
200
201 - public static Message CorruptFileFormat(string path, FileFormat format)
201 + public static Message CorruptFileFormat(string path, string format)
202 {
203 - return Message(null, Ids.CorruptFileFormat, "Attempted to load corrupt file from path: {0}. The file with format {1} contained unexpected content. Ensure the correct path was provided and that the file has not been incorrectly modified.", path, format.ToString().ToLowerInvariant());
203 + return Message(null, Ids.CorruptFileFormat, "Attempted to load corrupt file from path: {0}. The file with format {1} contained unexpected content. Ensure the correct path was provided and that the file has not been incorrectly modified.", path, format.ToLowerInvariant());
204 }
205
206 public static Message CreateCabAddFileFailed()
@@ -2049,9 +2049,9 @@ namespace WixToolset.Data
2049 return Message(null, Ids.UnexpectedFileExtension, "The file '{0}' has an unexpected extension. Expected one of the following: '{1}'.", fileName, expectedExtensions);
2050 }
2051
2052 - public static Message UnexpectedFileFormat(string path, FileFormat expectedFormat, FileFormat actualFormat)
2052 + public static Message UnexpectedFileFormat(string path, string expectedFormat, string actualFormat)
2053 {
2054 - return Message(null, Ids.UnexpectedFileFormat, "Unexpected file format loaded from path: {0}. The file was expected to be a {1} but was actually: {2}. Ensure the correct path was provided.", path, expectedFormat.ToString().ToLowerInvariant(), actualFormat.ToString().ToLowerInvariant());
2054 + return Message(null, Ids.UnexpectedFileFormat, "Unexpected file format loaded from path: {0}. The file was expected to be a {1} but was actually: {2}. Ensure the correct path was provided.", path, expectedFormat.ToLowerInvariant(), actualFormat.ToLowerInvariant());
2055 }
2056
2057 public static Message UnexpectedGroupChild(string parentType, string parentId, string childType, string childId)
src/WixToolset.Data/FileFormat.cs deleted
-19
@@ -1,19 +0,0 @@
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 - public enum FileFormat
6 - {
7 - Unknown,
8 -
9 - Wixobj,
10 -
11 - Wixlib,
12 -
13 - Wixout,
14 -
15 - Wixpdb,
16 -
17 - WixIR,
18 - }
19 -}
src/WixToolset.Data/FileStructure.cs deleted
-316
@@ -1,316 +0,0 @@
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.Collections.Generic;
7 - using System.Diagnostics;
8 - using System.IO;
9 - using System.Linq;
10 - using System.Text;
11 -
12 - /// <summary>
13 - /// Class that understands the standard file structures in the WiX toolset.
14 - /// </summary>
15 - public class FileStructure : IDisposable
16 - {
17 - private long dataStreamOffset;
18 - private long[] embeddedFileSizes;
19 - private Stream stream;
20 - private bool disposed;
21 -
22 - private static readonly Dictionary<string, FileFormat> SupportedFileFormats = new Dictionary<string, FileFormat>()
23 - {
24 - { "wir", FileFormat.WixIR },
25 - { "wixirf", FileFormat.WixIR },
26 - { "wixipl", FileFormat.WixIR },
27 - { "wixobj", FileFormat.Wixobj },
28 - { "wixlib", FileFormat.Wixlib },
29 - { "wixout", FileFormat.Wixout },
30 - { "wixpdb", FileFormat.Wixpdb },
31 - { "wixmst", FileFormat.Wixout },
32 - { "wixmsp", FileFormat.Wixout },
33 - };
34 -
35 - /// <summary>
36 - /// Use Create or Read to create a FileStructure.
37 - /// </summary>
38 - private FileStructure() { }
39 -
40 - /// <summary>
41 - /// Count of embedded files in the file structure.
42 - /// </summary>
43 - public int EmbeddedFileCount => this.embeddedFileSizes.Length;
44 -
45 - /// <summary>
46 - /// File format of the file structure.
47 - /// </summary>
48 - public FileFormat FileFormat { get; private set; }
49 -
50 - /// <summary>
51 - /// Creates a new file structure.
52 - /// </summary>
53 - /// <param name="stream">Stream to write the file structure to.</param>
54 - /// <param name="fileFormat">File format for the file structure.</param>
55 - /// <param name="embedFilePaths">Paths to files to embedd in the file structure.</param>
56 - /// <returns>Newly created file structure.</returns>
57 - public static FileStructure Create(Stream stream, FileFormat fileFormat, IEnumerable<string> embedFilePaths)
58 - {
59 - var fs = new FileStructure();
60 -
61 - using (var writer = new BinaryWriter(stream, Encoding.UTF8, true))
62 - {
63 - fs.WriteType(writer, fileFormat);
64 -
65 - fs.WriteEmbeddedFiles(writer, embedFilePaths?.ToArray() ?? Array.Empty<string>());
66 -
67 - // Remember the data stream offset, which is right after the embedded files have been written.
68 - fs.dataStreamOffset = stream.Position;
69 - }
70 -
71 - fs.stream = stream;
72 -
73 - return fs;
74 - }
75 -
76 - /// <summary>
77 - /// Reads a file structure from an open stream.
78 - /// </summary>
79 - /// <param name="stream">Stream to read from.</param>
80 - /// <returns>File structure populated from the stream.</returns>
81 - public static FileStructure Read(Stream stream)
82 - {
83 - var fs = new FileStructure();
84 -
85 - using (var reader = new BinaryReader(stream, Encoding.UTF8, true))
86 - {
87 - fs.FileFormat = FileStructure.ReadFileFormat(reader);
88 -
89 - if (fs.FileFormat != FileFormat.Unknown)
90 - {
91 - fs.embeddedFileSizes = FileStructure.ReadEmbeddedFileSizes(reader);
92 -
93 - // Remember the data stream offset, which is right after the embedded files have been written.
94 - fs.dataStreamOffset = stream.Position;
95 - foreach (long size in fs.embeddedFileSizes)
96 - {
97 - fs.dataStreamOffset += size;
98 - }
99 - }
100 - }
101 -
102 - fs.stream = stream;
103 -
104 - return fs;
105 - }
106 -
107 - /// <summary>
108 - /// Guess at the file format based on the file extension.
109 - /// </summary>
110 - /// <param name="extension">File extension to guess the file format for.</param>
111 - /// <returns>Best guess at file format.</returns>
112 - public static FileFormat GuessFileFormatFromExtension(string extension)
113 - {
114 - return FileStructure.SupportedFileFormats.TryGetValue(extension.TrimStart('.').ToLowerInvariant(), out var format) ? format : FileFormat.Unknown;
115 - }
116 -
117 - /// <summary>
118 - /// Probes a stream to determine the file format.
119 - /// </summary>
120 - /// <param name="stream">Stream to test.</param>
121 - /// <returns>The file format.</returns>
122 - public static FileFormat TestFileFormat(Stream stream)
123 - {
124 - FileFormat format = FileFormat.Unknown;
125 -
126 - long position = stream.Position;
127 -
128 - try
129 - {
130 - using (var reader = new BinaryReader(stream, Encoding.UTF8, true))
131 - {
132 - format = FileStructure.ReadFileFormat(reader);
133 - }
134 - }
135 - finally
136 - {
137 - stream.Seek(position, SeekOrigin.Begin);
138 - }
139 -
140 - return format;
141 - }
142 -
143 - /// <summary>
144 - /// Extracts an embedded file.
145 - /// </summary>
146 - /// <param name="embeddedIndex">Index to the file to extract.</param>
147 - /// <param name="outputPath">Path to write the extracted file to.</param>
148 - public void ExtractEmbeddedFile(int embeddedIndex, string outputPath)
149 - {
150 - if (this.EmbeddedFileCount <= embeddedIndex)
151 - {
152 - throw new ArgumentOutOfRangeException("embeddedIndex");
153 - }
154 -
155 - long header = 6 + 4 + (this.embeddedFileSizes.Length * 8); // skip the type + the count of embedded files + all the sizes of embedded files.
156 - long position = this.embeddedFileSizes.Take(embeddedIndex).Sum(); // skip to the embedded file we want.
157 - long size = this.embeddedFileSizes[embeddedIndex];
158 -
159 - this.stream.Seek(header + position, SeekOrigin.Begin);
160 -
161 - Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
162 -
163 - using (FileStream output = File.OpenWrite(outputPath))
164 - {
165 - int read;
166 - int total = 0;
167 - byte[] buffer = new byte[64 * 1024];
168 - while (0 < (read = this.stream.Read(buffer, 0, (int)Math.Min(buffer.Length, size - total))))
169 - {
170 - output.Write(buffer, 0, read);
171 - total += read;
172 - }
173 - }
174 - }
175 -
176 - /// <summary>
177 - /// Gets a non-closing stream to the data of the file.
178 - /// </summary>
179 - /// <returns>Stream to the data of the file.</returns>
180 - public Stream GetDataStream()
181 - {
182 - this.stream.Seek(this.dataStreamOffset, SeekOrigin.Begin);
183 - return new NonClosingStreamWrapper(this.stream);
184 - }
185 -
186 - /// <summary>
187 - /// Gets the data of the file as a string.
188 - /// </summary>
189 - /// <returns>String contents data of the file.</returns>
190 - public string GetData()
191 - {
192 - var bytes = new byte[this.stream.Length - this.dataStreamOffset];
193 -
194 - this.stream.Seek(this.dataStreamOffset, SeekOrigin.Begin);
195 - this.stream.Read(bytes, 0, bytes.Length);
196 -
197 - return Encoding.UTF8.GetString(bytes);
198 - }
199 -
200 - /// <summary>
201 - /// Disposes of the internal state of the file structure.
202 - /// </summary>
203 - public void Dispose()
204 - {
205 - Dispose(true);
206 - GC.SuppressFinalize(this);
207 - }
208 -
209 - /// <summary>
210 - /// Disposes of the internsl state of the file structure.
211 - /// </summary>
212 - /// <param name="disposing">True if disposing.</param>
213 - protected virtual void Dispose(bool disposing)
214 - {
215 - if (!this.disposed)
216 - {
217 - if (disposing)
218 - {
219 - if (null != this.stream)
220 - {
221 - // We do not own the stream, so we don't close it. We're just resetting our internal state.
222 - this.embeddedFileSizes = null;
223 - this.dataStreamOffset = 0;
224 - this.stream = null;
225 - }
226 - }
227 - }
228 -
229 - this.disposed = true;
230 - }
231 -
232 - private static FileFormat ReadFileFormat(BinaryReader reader)
233 - {
234 - FileFormat format = FileFormat.Unknown;
235 -
236 - string type = new string(reader.ReadChars(3));
237 - if (FileStructure.SupportedFileFormats.TryGetValue(type, out format))
238 - {
239 - return format;
240 - }
241 -
242 - type += new string(reader.ReadChars(3));
243 - FileStructure.SupportedFileFormats.TryGetValue(type, out format);
244 -
245 - return format;
246 - }
247 -
248 - private static long[] ReadEmbeddedFileSizes(BinaryReader reader)
249 - {
250 - uint count = reader.ReadUInt32();
251 -
252 - long[] embeddedFileSizes = new long[count];
253 -
254 - for (int i = 0; i < embeddedFileSizes.Length; ++i)
255 - {
256 - embeddedFileSizes[i] = (long)reader.ReadUInt64();
257 - }
258 -
259 - return embeddedFileSizes;
260 - }
261 -
262 - private BinaryWriter WriteType(BinaryWriter writer, FileFormat fileFormat)
263 - {
264 - string type = null;
265 - foreach (var supported in FileStructure.SupportedFileFormats)
266 - {
267 - if (supported.Value.Equals(fileFormat))
268 - {
269 - type = supported.Key;
270 - break;
271 - }
272 - }
273 -
274 - if (String.IsNullOrEmpty(type))
275 - {
276 - throw new ArgumentException("Unknown file format type", "fileFormat");
277 - }
278 -
279 - this.FileFormat = fileFormat;
280 -
281 - Debug.Assert(3 == type.ToCharArray().Length || 6 == type.ToCharArray().Length);
282 - writer.Write(type.ToCharArray());
283 - return writer;
284 - }
285 -
286 - private BinaryWriter WriteEmbeddedFiles(BinaryWriter writer, string[] embedFilePaths)
287 - {
288 - // First write the count of embedded files as a Uint32;
289 - writer.Write((uint)embedFilePaths.Length);
290 -
291 - this.embeddedFileSizes = new long[embedFilePaths.Length];
292 -
293 - // Next write out the size of each file as a Uint64 in order.
294 - FileInfo[] files = new FileInfo[embedFilePaths.Length];
295 - for (int i = 0; i < embedFilePaths.Length; ++i)
296 - {
297 - files[i] = new FileInfo(embedFilePaths[i]);
298 -
299 - this.embeddedFileSizes[i] = files[i].Length;
300 - writer.Write((ulong)this.embeddedFileSizes[i]);
301 - }
302 -
303 - // Next write out the content of each file *after* the sizes of
304 - // *all* of the files were written.
305 - foreach (FileInfo file in files)
306 - {
307 - using (FileStream stream = file.OpenRead())
308 - {
309 - stream.CopyTo(writer.BaseStream);
310 - }
311 - }
312 -
313 - return writer;
314 - }
315 - }
316 -}
src/WixToolset.Data/Intermediate.cs
+102 -199
@@ -14,8 +14,8 @@ namespace WixToolset.Data
14 /// </summary>
15 public sealed class Intermediate
16 {
17 - public const string XmlNamespaceUri = "http://wixtoolset.org/schemas/v4/wixobj";
17 private static readonly Version CurrentVersion = new Version("4.0.0.0");
18 + private const string WixOutputStreamName = "wix-ir.json";
19
20 private readonly Dictionary<string, Localization> localizationsByCulture;
21
@@ -25,15 +25,13 @@ namespace WixToolset.Data
25 public Intermediate()
26 {
27 this.Id = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).TrimEnd('=').Replace('+', '.').Replace('/', '_');
28 - this.EmbedFilePaths = new List<string>();
28 this.localizationsByCulture = new Dictionary<string, Localization>(StringComparer.OrdinalIgnoreCase);
29 this.Sections = new List<IntermediateSection>();
30 }
31
33 - public Intermediate(string id, IEnumerable<IntermediateSection> sections, IDictionary<string, Localization> localizationsByCulture, IEnumerable<string> embedFilePaths)
32 + public Intermediate(string id, IEnumerable<IntermediateSection> sections, IDictionary<string, Localization> localizationsByCulture)
33 {
34 this.Id = id;
36 - this.EmbedFilePaths = (embedFilePaths != null) ? new List<string>(embedFilePaths) : new List<string>();
35 this.localizationsByCulture = (localizationsByCulture != null) ? new Dictionary<string, Localization>(localizationsByCulture, StringComparer.OrdinalIgnoreCase) : new Dictionary<string, Localization>(StringComparer.OrdinalIgnoreCase);
36 this.Sections = (sections != null) ? new List<IntermediateSection>(sections) : new List<IntermediateSection>();
37 }
@@ -43,11 +41,6 @@ namespace WixToolset.Data
41 /// </summary>
42 public string Id { get; }
43
46 - /// <summary>
47 - /// Get the embed file paths in this intermediate.
48 - /// </summary>
49 - public IList<string> EmbedFilePaths { get; }
50 -
44 /// <summary>
45 /// Get the localizations contained in this intermediate.
46 /// </summary>
@@ -66,12 +59,8 @@ namespace WixToolset.Data
59 /// <returns>Returns the loaded intermediate.</returns>
60 public static Intermediate Load(string path, bool suppressVersionCheck = false)
61 {
69 - using (var stream = File.OpenRead(path))
70 - {
71 - var uri = new Uri(Path.GetFullPath(path));
72 - var creator = new SimpleTupleDefinitionCreator();
73 - return Intermediate.LoadIntermediate(stream, uri, creator, suppressVersionCheck);
74 - }
62 + var creator = new SimpleTupleDefinitionCreator();
63 + return Intermediate.Load(path, creator, suppressVersionCheck);
64 }
65
66 /// <summary>
@@ -97,13 +86,9 @@ namespace WixToolset.Data
86 /// <returns>Returns the loaded intermediate.</returns>
87 public static Intermediate Load(Assembly assembly, string resourceName, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
88 {
100 - using (var resourceStream = assembly.GetManifestResourceStream(resourceName))
89 + using (var wixout = WixOutput.Read(assembly, resourceName))
90 {
102 - var uriBuilder = new UriBuilder(assembly.CodeBase);
103 - uriBuilder.Scheme = "embeddedresource";
104 - uriBuilder.Fragment = resourceName;
105 -
106 - return Intermediate.LoadIntermediate(resourceStream, uriBuilder.Uri, creator, suppressVersionCheck);
91 + return Intermediate.LoadIntermediate(wixout, creator, suppressVersionCheck);
92 }
93 }
94
@@ -116,11 +101,9 @@ namespace WixToolset.Data
101 /// <returns>Returns the loaded intermediate.</returns>
102 public static Intermediate Load(string path, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
103 {
119 - using (var stream = File.OpenRead(path))
104 + using (var wixout = WixOutput.Read(path))
105 {
121 - var uri = new Uri(Path.GetFullPath(path));
122 -
123 - return Intermediate.LoadIntermediate(stream, uri, creator, suppressVersionCheck);
106 + return Intermediate.LoadIntermediate(wixout, creator, suppressVersionCheck);
107 }
108 }
109
@@ -133,7 +116,6 @@ namespace WixToolset.Data
116 public static IEnumerable<Intermediate> Load(IEnumerable<string> intermediateFiles)
117 {
118 var creator = new SimpleTupleDefinitionCreator();
136 -
119 return Intermediate.Load(intermediateFiles, creator);
120 }
121
@@ -151,15 +133,14 @@ namespace WixToolset.Data
133
134 foreach (var path in intermediateFiles)
135 {
154 - using (var stream = File.OpenRead(path))
136 + using (var wixout = WixOutput.Read(path))
137 {
156 - var uri = new Uri(Path.GetFullPath(path));
157 -
158 - var json = Intermediate.LoadJson(stream, uri, suppressVersionCheck);
138 + var data = wixout.GetData(WixOutputStreamName);
139 + var json = Intermediate.LoadJson(data, wixout.Uri, suppressVersionCheck);
140
141 Intermediate.LoadDefinitions(json, creator);
142
162 - jsons.Enqueue(new JsonWithPath { Json = json, Path = uri });
143 + jsons.Enqueue(new JsonWithPath { Json = json, Path = wixout.Uri });
144 }
145 }
146
@@ -183,55 +164,21 @@ namespace WixToolset.Data
164 {
165 Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
166
186 - using (var stream = File.Create(path))
187 - using (var fs = FileStructure.Create(stream, FileFormat.WixIR, this.EmbedFilePaths))
188 - using (var writer = new StreamWriter(fs.GetDataStream()))
167 + using (var wixout = WixOutput.Create(path))
168 {
190 - var jsonObject = new JsonObject
191 - {
192 - { "id", this.Id },
193 - { "version", Intermediate.CurrentVersion.ToString() }
194 - };
195 -
196 - var sectionsJson = new JsonArray(this.Sections.Count);
197 - foreach (var section in this.Sections)
198 - {
199 - var sectionJson = section.Serialize();
200 - sectionsJson.Add(sectionJson);
201 - }
202 -
203 - jsonObject.Add("sections", sectionsJson);
204 -
205 - var customDefinitions = this.GetCustomDefinitionsInSections();
206 -
207 - if (customDefinitions.Count > 0)
208 - {
209 - var customDefinitionsJson = new JsonArray(customDefinitions.Count);
210 -
211 - foreach (var kvp in customDefinitions.OrderBy(d => d.Key))
212 - {
213 - var customDefinitionJson = kvp.Value.Serialize();
214 - customDefinitionsJson.Add(customDefinitionJson);
215 - }
216 -
217 - jsonObject.Add("definitions", customDefinitionsJson);
218 - }
219 -
220 - if (this.Localizations.Any())
221 - {
222 - var localizationsJson = new JsonArray();
223 - foreach (var localization in this.Localizations)
224 - {
225 - var localizationJson = localization.Serialize();
226 - localizationsJson.Add(localizationJson);
227 - }
169 + this.Save(wixout);
170 + }
171 + }
172
229 - jsonObject.Add("localizations", localizationsJson);
230 - }
173 + /// <summary>
174 + /// Saves an intermediate to a path on disk.
175 + /// </summary>
176 + /// <param name="path">Path to save intermediate file to disk.</param>
177 + public void Save(WixOutput wixout)
178 + {
179 + this.SaveEmbedFiles(wixout);
180
232 - var json = SimpleJson.SerializeObject(jsonObject);
233 - writer.Write(json);
234 - }
181 + this.SaveIR(wixout);
182 }
183
184 /// <summary>
@@ -242,13 +189,14 @@ namespace WixToolset.Data
189 /// <param name="creator">ITupleDefinitionCreator to use when reconstituting the intermediate.</param>
190 /// <param name="suppressVersionCheck">Suppress checking for wix.dll version mismatches.</param>
191 /// <returns>Returns the loaded intermediate.</returns>
245 - private static Intermediate LoadIntermediate(Stream stream, Uri baseUri, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
192 + private static Intermediate LoadIntermediate(WixOutput wixout, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
193 {
247 - var json = Intermediate.LoadJson(stream, baseUri, suppressVersionCheck);
194 + var data = wixout.GetData(WixOutputStreamName);
195 + var json = Intermediate.LoadJson(data, wixout.Uri, suppressVersionCheck);
196
197 Intermediate.LoadDefinitions(json, creator);
198
251 - return Intermediate.FinalizeLoad(json, baseUri, creator);
199 + return Intermediate.FinalizeLoad(json, wixout.Uri, creator);
200 }
201
202 /// <summary>
@@ -258,19 +206,9 @@ namespace WixToolset.Data
206 /// <param name="baseUri">Path name of intermediate file.</param>
207 /// <param name="suppressVersionCheck">Suppress checking for wix.dll version mismatches.</param>
208 /// <returns>Returns the loaded json.</returns>
261 - private static JsonObject LoadJson(Stream stream, Uri baseUri, bool suppressVersionCheck)
209 + private static JsonObject LoadJson(string json, Uri baseUri, bool suppressVersionCheck)
210 {
263 - JsonObject jsonObject;
264 - using (var fs = FileStructure.Read(stream))
265 - {
266 - if (FileFormat.WixIR != fs.FileFormat)
267 - {
268 - throw new WixUnexpectedFileFormatException(baseUri.LocalPath, FileFormat.WixIR, fs.FileFormat);
269 - }
270 -
271 - var json = fs.GetData();
272 - jsonObject = SimpleJson.DeserializeObject(json) as JsonObject;
273 - }
211 + var jsonObject = SimpleJson.DeserializeObject(json) as JsonObject;
212
213 if (!suppressVersionCheck)
214 {
@@ -333,153 +271,118 @@ namespace WixToolset.Data
271 localizations.Add(localization.Culture, localization);
272 }
273
336 - return new Intermediate(id, sections, localizations, null);
274 + return new Intermediate(id, sections, localizations);
275 }
276
339 -#if false
340 - /// <summary>
341 - /// Loads an intermediate from a path on disk.
342 - /// </summary>
343 - /// <param name="path">Path to intermediate file saved on disk.</param>
344 - /// <param name="tableDefinitions">Collection containing TableDefinitions to use when reconstituting the intermediate.</param>
345 - /// <param name="suppressVersionCheck">Suppress checking for wix.dll version mismatches.</param>
346 - /// <returns>Returns the loaded intermediate.</returns>
347 - public static Intermediate Load(string path, TableDefinitionCollection tableDefinitions, bool suppressVersionCheck)
277 + private void SaveEmbedFiles(WixOutput wixout)
278 {
349 - using (FileStream stream = File.OpenRead(path))
350 - using (FileStructure fs = FileStructure.Read(stream))
279 + var embeddedFields = this.Sections.SelectMany(s => s.Tuples)
280 + .SelectMany(t => t.Fields)
281 + .Where(f => f?.Type == IntermediateFieldType.Path)
282 + .Select(f => f.AsPath())
283 + .Where(f => f.Embed)
284 + .ToList();
285 +
286 + var savedEmbedFields = new Dictionary<string, IntermediateFieldPathValue>(StringComparer.OrdinalIgnoreCase);
287 + var uniqueEntryNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
288 +
289 + foreach (var embeddedField in embeddedFields)
290 {
352 - if (FileFormat.Wixobj != fs.FileFormat)
291 + var key = String.Concat(embeddedField.BaseUri?.AbsoluteUri, "?", embeddedField.Path);
292 +
293 + if (savedEmbedFields.TryGetValue(key, out var existing))
294 {
354 - throw new WixUnexpectedFileFormatException(path, FileFormat.Wixobj, fs.FileFormat);
295 + embeddedField.Path = existing.Path;
296 }
356 -
357 - Uri uri = new Uri(Path.GetFullPath(path));
358 - using (XmlReader reader = XmlReader.Create(fs.GetDataStream(), null, uri.AbsoluteUri))
297 + else
298 {
360 - try
299 + var entryName = CalculateUniqueEntryName(uniqueEntryNames, embeddedField.Path);
300 +
301 + if (embeddedField.BaseUri == null)
302 {
362 - reader.MoveToContent();
363 - return Intermediate.Read(reader, tableDefinitions, suppressVersionCheck);
303 + wixout.ImportDataStream(entryName, embeddedField.Path);
304 }
365 - catch (XmlException xe)
305 + else // open the container specified in baseUri and copy the correct stream out of it.
306 {
367 - throw new WixCorruptFileException(path, fs.FileFormat, xe);
307 + using (var otherWixout = WixOutput.Read(embeddedField.BaseUri))
308 + using (var stream = otherWixout.GetDataStream(embeddedField.Path))
309 + using (var target = wixout.CreateDataStream(entryName))
310 + {
311 + stream.CopyTo(target);
312 + }
313 }
369 - }
370 - }
371 - }
314
373 - /// <summary>
374 - /// Saves an intermediate to a path on disk.
375 - /// </summary>
376 - /// <param name="path">Path to save intermediate file to disk.</param>
377 - public void Save(string path)
378 - {
379 - Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
315 + embeddedField.Path = entryName;
316
381 - using (var stream = File.Create(path))
382 - using (var fs = FileStructure.Create(stream, FileFormat.Wixobj, null))
383 - using (var writer = XmlWriter.Create(fs.GetDataStream()))
384 - {
385 - writer.WriteStartDocument();
386 - this.Write(writer);
387 - writer.WriteEndDocument();
317 + savedEmbedFields.Add(key, embeddedField);
318 + }
319 }
320 }
321
391 - /// <summary>
392 - /// Parse an intermediate from an XML format.
393 - /// </summary>
394 - /// <param name="reader">XmlReader where the intermediate is persisted.</param>
395 - /// <param name="tableDefinitions">TableDefinitions to use in the intermediate.</param>
396 - /// <param name="suppressVersionCheck">Suppress checking for wix.dll version mismatch.</param>
397 - /// <returns>The parsed Intermediate.</returns>
398 - private static Intermediate Read(XmlReader reader, TableDefinitionCollection tableDefinitions, bool suppressVersionCheck)
322 + private void SaveIR(WixOutput wixout)
323 {
400 - if ("wixObject" != reader.LocalName)
324 + using (var writer = new StreamWriter(wixout.CreateDataStream(WixOutputStreamName)))
325 {
402 - throw new XmlException();
403 - }
404 -
405 - bool empty = reader.IsEmptyElement;
406 - Version objVersion = null;
407 - string id = null;
326 + var jsonObject = new JsonObject
327 + {
328 + { "id", this.Id },
329 + { "version", Intermediate.CurrentVersion.ToString() }
330 + };
331
409 - while (reader.MoveToNextAttribute())
410 - {
411 - switch (reader.LocalName)
332 + var sectionsJson = new JsonArray(this.Sections.Count);
333 + foreach (var section in this.Sections)
334 {
413 - case "version":
414 - objVersion = new Version(reader.Value);
415 - break;
416 - case "id":
417 - id = reader.Value;
418 - break;
335 + var sectionJson = section.Serialize();
336 + sectionsJson.Add(sectionJson);
337 }
420 - }
421 -
422 - if (!suppressVersionCheck && null != objVersion && !Intermediate.CurrentVersion.Equals(objVersion))
423 - {
424 - throw new WixException(WixDataErrors.VersionMismatch(SourceLineNumber.CreateFromUri(reader.BaseURI), "object", objVersion.ToString(), Intermediate.CurrentVersion.ToString()));
425 - }
338
427 - Intermediate intermediate = new Intermediate();
428 - intermediate.id = id;
339 + jsonObject.Add("sections", sectionsJson);
340
430 - if (!empty)
431 - {
432 - bool done = false;
341 + var customDefinitions = this.GetCustomDefinitionsInSections();
342
434 - while (!done && reader.Read())
343 + if (customDefinitions.Count > 0)
344 {
436 - switch (reader.NodeType)
345 + var customDefinitionsJson = new JsonArray(customDefinitions.Count);
346 +
347 + foreach (var kvp in customDefinitions.OrderBy(d => d.Key))
348 {
438 - case XmlNodeType.Element:
439 - switch (reader.LocalName)
440 - {
441 - case "section":
442 - intermediate.AddSection(Section.Read(reader, tableDefinitions));
443 - break;
444 - default:
445 - throw new XmlException();
446 - }
447 - break;
448 - case XmlNodeType.EndElement:
449 - done = true;
450 - break;
349 + var customDefinitionJson = kvp.Value.Serialize();
350 + customDefinitionsJson.Add(customDefinitionJson);
351 }
352 +
353 + jsonObject.Add("definitions", customDefinitionsJson);
354 }
355
454 - if (!done)
356 + if (this.Localizations.Any())
357 {
456 - throw new XmlException();
358 + var localizationsJson = new JsonArray();
359 + foreach (var localization in this.Localizations)
360 + {
361 + var localizationJson = localization.Serialize();
362 + localizationsJson.Add(localizationJson);
363 + }
364 +
365 + jsonObject.Add("localizations", localizationsJson);
366 }
458 - }
367
460 - return intermediate;
368 + var json = SimpleJson.SerializeObject(jsonObject);
369 + writer.Write(json);
370 + }
371 }
372
463 - /// <summary>
464 - /// Persists an intermediate in an XML format.
465 - /// </summary>
466 - /// <param name="writer">XmlWriter where the Intermediate should persist itself as XML.</param>
467 - private void Write(XmlWriter writer)
373 + private static string CalculateUniqueEntryName(ISet<string> entryNames, string path)
374 {
469 - writer.WriteStartElement("wixObject", XmlNamespaceUri);
470 -
471 - writer.WriteAttributeString("version", Intermediate.CurrentVersion.ToString());
472 -
473 - writer.WriteAttributeString("id", this.id);
375 + var filename = Path.GetFileName(path);
376 + var entryName = "wix-ir/" + filename;
377 + var i = 0;
378
475 - foreach (Section section in this.Sections)
379 + while (!entryNames.Add(entryName))
380 {
477 - section.Write(writer);
381 + entryName = $"wix-ir/{filename}-{++i}";
382 }
383
480 - writer.WriteEndElement();
384 + return entryName;
385 }
482 -#endif
386
387 private Dictionary<string, IntermediateTupleDefinition> GetCustomDefinitionsInSections()
388 {
src/WixToolset.Data/IntermediateFieldContext.cs
+3 -3
@@ -1,4 +1,4 @@
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.
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 {
@@ -6,7 +6,7 @@ namespace WixToolset.Data
6
7 public class IntermediateFieldContext : IDisposable
8 {
9 - private string previous;
9 + private readonly string previous;
10 private bool disposed;
11
12 public IntermediateFieldContext(string context)
@@ -18,7 +18,7 @@ namespace WixToolset.Data
18
19 public void Dispose()
20 {
21 - Dispose(true);
21 + this.Dispose(true);
22 GC.SuppressFinalize(this);
23 }
24
src/WixToolset.Data/IntermediateFieldExtensions.cs
+2 -2
@@ -386,7 +386,7 @@ namespace WixToolset.Data
386
387 default:
388 throw new ArgumentOutOfRangeException(nameof(value), $"Unknown intermediate field type: {value.GetType()}");
389 - };
389 + }
390 }
391
392 return AssignFieldValue(field, data);
@@ -455,7 +455,7 @@ namespace WixToolset.Data
455
456 default:
457 throw new ArgumentOutOfRangeException(nameof(value), $"Unknown intermediate field type: {value.GetType()}");
458 - };
458 + }
459 }
460
461 return AssignFieldValue(field, data);
src/WixToolset.Data/IntermediateFieldPathValue.cs
+3 -4
@@ -1,4 +1,4 @@
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.
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 {
@@ -7,10 +7,9 @@ namespace WixToolset.Data
7 public class IntermediateFieldPathValue
8 {
9 /// <summary>
10 - /// Gets or sets the index of the embedded file in a library.
10 + /// Indicates whether to embed the path to the file when the intermediate field is saved.
11 /// </summary>
12 - /// <value>The index of the embedded file.</value>
13 - public int? EmbeddedFileIndex { get; set; }
12 + public bool Embed { get; set; }
13
14 /// <summary>
15 /// Gets the base URI of the path field.
src/WixToolset.Data/IntermediateFieldValue.cs
+8 -8
@@ -76,19 +76,19 @@ namespace WixToolset.Data
76 break;
77
78 case JsonObject jsonData:
79 - jsonData.TryGetValue("embeddedIndex", out var embeddedIndex);
79 + jsonData.TryGetValue("embed", out var embed);
80
81 value = new IntermediateFieldPathValue
82 {
83 - BaseUri = (embeddedIndex == null) ? null : baseUri,
84 - EmbeddedFileIndex = (embeddedIndex == null) ? null : (int?)Convert.ToInt32(embeddedIndex),
83 + BaseUri = (embed != null) ? baseUri : null,
84 + Embed = embed != null,
85 Path = jsonData.GetValueOrDefault<string>("path"),
86 };
87 break;
88
89 - // Nothing to do for this case, so leave it out.
90 - // case string stringData:
91 - // break;
89 + // Nothing to do for this case, so leave it out.
90 + // case string stringData:
91 + // break;
92 }
93
94 var previousValueJson = jsonObject.GetValueOrDefault<JsonObject>("prev");
@@ -117,9 +117,9 @@ namespace WixToolset.Data
117
118 // pathField.BaseUri is set during load, not saved.
119
120 - if (pathField.EmbeddedFileIndex.HasValue)
120 + if (pathField.Embed)
121 {
122 - jsonData.Add("embeddedIndex", pathField.EmbeddedFileIndex.Value);
122 + jsonData.Add("embed", "true");
123 }
124
125 if (!String.IsNullOrEmpty(pathField.Path))
src/WixToolset.Data/NonClosingStreamWrapper.cs deleted
-84
@@ -1,84 +0,0 @@
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 -
8 - /// <summary>
9 - /// Wrapper around stream to prevent other streams (like BinaryReader/Writer) from prematurely
10 - /// closing a parent stream.
11 - /// </summary>
12 - internal class NonClosingStreamWrapper : Stream
13 - {
14 - private Stream stream;
15 -
16 - public NonClosingStreamWrapper(Stream stream)
17 - {
18 - this.stream = stream;
19 - }
20 -
21 - public override bool CanRead => this.stream.CanRead;
22 -
23 - public override bool CanSeek => this.stream.CanSeek;
24 -
25 - public override bool CanTimeout => this.stream.CanTimeout;
26 -
27 - public override bool CanWrite => this.stream.CanWrite;
28 -
29 - public override long Length => this.stream.Length;
30 -
31 - public override long Position
32 - {
33 - get => this.stream.Position;
34 - set => this.stream.Position = value;
35 - }
36 -
37 - public override int ReadTimeout
38 - {
39 - get => this.stream.ReadTimeout;
40 - set => this.stream.ReadTimeout = value;
41 - }
42 -
43 - public override int WriteTimeout
44 - {
45 - get => this.stream.WriteTimeout;
46 - set => this.stream.WriteTimeout = value;
47 - }
48 -
49 - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) => this.stream.BeginRead(buffer, offset, count, callback, state);
50 -
51 - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) => this.stream.BeginWrite(buffer, offset, count, callback, state);
52 -
53 - public override int EndRead(IAsyncResult asyncResult) => this.stream.EndRead(asyncResult);
54 -
55 - public override void EndWrite(IAsyncResult asyncResult) => this.stream.EndWrite(asyncResult);
56 -
57 - public override void Flush() => this.stream.Flush();
58 -
59 - public override int Read(byte[] buffer, int offset, int count) => this.stream.Read(buffer, offset, count);
60 -
61 - public override int ReadByte() => this.stream.ReadByte();
62 -
63 - public override long Seek(long offset, SeekOrigin origin) => this.stream.Seek(offset, origin);
64 -
65 - public override void SetLength(long value) => this.stream.SetLength(value);
66 -
67 - public override void Write(byte[] buffer, int offset, int count) => this.stream.Write(buffer, offset, count);
68 -
69 - public override void WriteByte(byte value) => this.stream.WriteByte(value);
70 -
71 - public override void Close()
72 - {
73 - // Do not pass through the call since this is what we are overriding.
74 - }
75 -
76 - protected override void Dispose(bool disposing)
77 - {
78 - if (disposing)
79 - {
80 - this.stream.Flush();
81 - }
82 - }
83 - }
84 -}
src/WixToolset.Data/WindowsInstaller/Field.cs
+2 -2
@@ -154,7 +154,7 @@ namespace WixToolset.Data.WindowsInstaller
154 // be enhanced if that ever changes.
155 if (value is int || value.GetType().IsEnum)
156 {
157 - int intValue = (int)value;
157 + var intValue = (int)value;
158
159 // validate the value against the minimum allowed value
160 if (column.MinValue.HasValue && column.MinValue > intValue)
@@ -275,7 +275,7 @@ namespace WixToolset.Data.WindowsInstaller
275 /// <param name="writer">XmlWriter where the Field should persist itself as XML.</param>
276 internal virtual void Write(XmlWriter writer)
277 {
278 - writer.WriteStartElement("field", Intermediate.XmlNamespaceUri);
278 + writer.WriteStartElement("field", Output.XmlNamespaceUri);
279
280 if (this.Modified)
281 {
src/WixToolset.Data/WindowsInstaller/ObjectField.cs
+1 -1
@@ -130,7 +130,7 @@ namespace WixToolset.Data.WindowsInstaller
130 /// <param name="writer">XmlWriter where the Field should persist itself as XML.</param>
131 internal override void Write(XmlWriter writer)
132 {
133 - writer.WriteStartElement("field", Intermediate.XmlNamespaceUri);
133 + writer.WriteStartElement("field", Output.XmlNamespaceUri);
134
135 if (this.EmbeddedFileIndex.HasValue)
136 {
src/WixToolset.Data/WindowsInstaller/Output.cs
+22 -34
@@ -5,7 +5,6 @@ namespace WixToolset.Data.WindowsInstaller
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 - using System.IO;
8 using System.Linq;
9 using System.Xml;
10
@@ -16,6 +15,7 @@ namespace WixToolset.Data.WindowsInstaller
15 {
16 public const string XmlNamespaceUri = "http://wixtoolset.org/schemas/v4/wixout";
17 private static readonly Version CurrentVersion = new Version("4.0.0.0");
18 + private const string WixOutputStreamName = "wix-wi.xml";
19
20 /// <summary>
21 /// Creates a new empty output object.
@@ -66,41 +66,29 @@ namespace WixToolset.Data.WindowsInstaller
66 /// <returns>Output object.</returns>
67 public static Output Load(string path, bool suppressVersionCheck)
68 {
69 - using (FileStream stream = File.OpenRead(path))
70 - using (FileStructure fs = FileStructure.Read(stream))
69 + using (var wixout = WixOutput.Read(path))
70 + using (var stream = wixout.GetDataStream(WixOutputStreamName))
71 + using (var reader = XmlReader.Create(stream, null, wixout.Uri.AbsoluteUri))
72 {
72 - if (FileFormat.Wixout != fs.FileFormat)
73 + try
74 {
74 - throw new WixUnexpectedFileFormatException(path, FileFormat.Wixout, fs.FileFormat);
75 + reader.MoveToContent();
76 + return Output.Read(reader, suppressVersionCheck);
77 }
76 -
77 - Uri uri = new Uri(Path.GetFullPath(path));
78 - using (XmlReader reader = XmlReader.Create(fs.GetDataStream(), null, uri.AbsoluteUri))
78 + catch (XmlException xe)
79 {
80 - try
81 - {
82 - reader.MoveToContent();
83 - return Output.Read(reader, suppressVersionCheck);
84 - }
85 - catch (XmlException xe)
86 - {
87 - throw new WixCorruptFileException(path, fs.FileFormat, xe);
88 - }
80 + throw new WixCorruptFileException(path, "wixout", xe);
81 }
82 }
83 }
84
85 /// <summary>
94 - /// Saves an output to a path on disk.
86 + /// Saves an output to a <c>WixOutput</c> container.
87 /// </summary>
96 - /// <param name="path">Path to save output file to on disk.</param>
97 - public void Save(string path)
88 + /// <param name="wixout">Container to save to.</param>
89 + public void Save(WixOutput wixout)
90 {
99 - Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
100 -
101 - using (FileStream stream = File.Create(path))
102 - using (FileStructure fs = FileStructure.Create(stream, FileFormat.Wixout, null))
103 - using (XmlWriter writer = XmlWriter.Create(fs.GetDataStream()))
91 + using (var writer = XmlWriter.Create(wixout.CreateDataStream(WixOutputStreamName)))
92 {
93 writer.WriteStartDocument();
94 this.Write(writer);
@@ -121,8 +109,8 @@ namespace WixToolset.Data.WindowsInstaller
109 throw new XmlException();
110 }
111
124 - bool empty = reader.IsEmptyElement;
125 - Output output = new Output(SourceLineNumber.CreateFromUri(reader.BaseURI));
112 + var empty = reader.IsEmptyElement;
113 + var output = new Output(SourceLineNumber.CreateFromUri(reader.BaseURI));
114 Version version = null;
115
116 while (reader.MoveToNextAttribute())
@@ -170,10 +158,10 @@ namespace WixToolset.Data.WindowsInstaller
158
159 // loop through the rest of the xml building up the Output object
160 TableDefinitionCollection tableDefinitions = null;
173 - List<Table> tables = new List<Table>();
161 + var tables = new List<Table>();
162 if (!empty)
163 {
176 - bool done = false;
164 + var done = false;
165
166 // loop through all the fields in a row
167 while (!done && reader.Read())
@@ -224,7 +212,7 @@ namespace WixToolset.Data.WindowsInstaller
212 /// <returns>The table in this output.</returns>
213 public Table EnsureTable(TableDefinition tableDefinition)
214 {
227 - if (!this.Tables.TryGetTable(tableDefinition.Name, out Table table))
215 + if (!this.Tables.TryGetTable(tableDefinition.Name, out var table))
216 {
217 table = new Table(tableDefinition);
218 this.Tables.Add(table);
@@ -251,19 +239,19 @@ namespace WixToolset.Data.WindowsInstaller
239 writer.WriteAttributeString("version", Output.CurrentVersion.ToString());
240
241 // Collect all the table definitions and write them.
254 - TableDefinitionCollection tableDefinitions = new TableDefinitionCollection();
255 - foreach (Table table in this.Tables)
242 + var tableDefinitions = new TableDefinitionCollection();
243 + foreach (var table in this.Tables)
244 {
245 tableDefinitions.Add(table.Definition);
246 }
247 tableDefinitions.Write(writer);
248
261 - foreach (Table table in this.Tables.OrderBy(t => t.Name))
249 + foreach (var table in this.Tables.OrderBy(t => t.Name))
250 {
251 table.Write(writer);
252 }
253
266 - foreach (SubStorage subStorage in this.SubStorages)
254 + foreach (var subStorage in this.SubStorages)
255 {
256 subStorage.Write(writer);
257 }
src/WixToolset.Data/WindowsInstaller/Pdb.cs deleted
-155
@@ -1,155 +0,0 @@
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.WindowsInstaller
4 -{
5 - using System;
6 - using System.IO;
7 - using System.Xml;
8 -
9 - /// <summary>
10 - /// Pdb generated by the binder.
11 - /// </summary>
12 - public sealed class Pdb
13 - {
14 - public const string XmlNamespaceUri = "http://wixtoolset.org/schemas/v4/wixpdb";
15 - private static readonly Version CurrentVersion = new Version("4.0.0.0");
16 -
17 - /// <summary>
18 - /// Gets or sets the output that is a part of this pdb.
19 - /// </summary>
20 - /// <value>Type of the output.</value>
21 - public Output Output { get; set; }
22 -
23 - /// <summary>
24 - /// Loads a pdb from a path on disk.
25 - /// </summary>
26 - /// <param name="path">Path to pdb file saved on disk.</param>
27 - /// <param name="suppressVersionCheck">Suppresses wix.dll version mismatch check.</param>
28 - /// <returns>Pdb pdb.</returns>
29 - public static Pdb Load(string path, bool suppressVersionCheck)
30 - {
31 - using (FileStream stream = File.OpenRead(path))
32 - using (FileStructure fs = FileStructure.Read(stream))
33 - {
34 - if (FileFormat.Wixpdb != fs.FileFormat)
35 - {
36 - throw new WixUnexpectedFileFormatException(path, FileFormat.Wixpdb, fs.FileFormat);
37 - }
38 -
39 - Uri uri = new Uri(Path.GetFullPath(path));
40 - using (XmlReader reader = XmlReader.Create(fs.GetDataStream(), null, uri.AbsoluteUri))
41 - {
42 - try
43 - {
44 - reader.MoveToContent();
45 - return Pdb.Read(reader, suppressVersionCheck);
46 - }
47 - catch (XmlException xe)
48 - {
49 - throw new WixCorruptFileException(path, fs.FileFormat, xe);
50 - }
51 - }
52 - }
53 - }
54 -
55 - /// <summary>
56 - /// Saves a pdb to a path on disk.
57 - /// </summary>
58 - /// <param name="path">Path to save pdb file to on disk.</param>
59 - public void Save(string path)
60 - {
61 - Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
62 -
63 - using (FileStream stream = File.Create(path))
64 - using (FileStructure fs = FileStructure.Create(stream, FileFormat.Wixpdb, null))
65 - using (XmlWriter writer = XmlWriter.Create(fs.GetDataStream()))
66 - {
67 - writer.WriteStartDocument();
68 - this.Write(writer);
69 - writer.WriteEndDocument();
70 - }
71 - }
72 -
73 - /// <summary>
74 - /// Processes an XmlReader and builds up the pdb object.
75 - /// </summary>
76 - /// <param name="reader">Reader to get data from.</param>
77 - /// <param name="suppressVersionCheck">Suppresses wix.dll version mismatch check.</param>
78 - /// <returns>The Pdb represented by the Xml.</returns>
79 - internal static Pdb Read(XmlReader reader, bool suppressVersionCheck)
80 - {
81 - if ("wixPdb" != reader.LocalName)
82 - {
83 - throw new XmlException();
84 - }
85 -
86 - bool empty = reader.IsEmptyElement;
87 - Pdb pdb = new Pdb();
88 - Version version = null;
89 -
90 - while (reader.MoveToNextAttribute())
91 - {
92 - switch (reader.LocalName)
93 - {
94 - case "version":
95 - version = new Version(reader.Value);
96 - break;
97 - }
98 - }
99 -
100 - if (!suppressVersionCheck && null != version && !Pdb.CurrentVersion.Equals(version))
101 - {
102 - throw new WixException(ErrorMessages.VersionMismatch(SourceLineNumber.CreateFromUri(reader.BaseURI), "wixPdb", version.ToString(), Pdb.CurrentVersion.ToString()));
103 - }
104 -
105 - // loop through the rest of the pdb building up the Output object
106 - if (!empty)
107 - {
108 - bool done = false;
109 -
110 - // loop through all the fields in a row
111 - while (!done && reader.Read())
112 - {
113 - switch (reader.NodeType)
114 - {
115 - case XmlNodeType.Element:
116 - switch (reader.LocalName)
117 - {
118 - case "wixOutput":
119 - pdb.Output = Output.Read(reader, suppressVersionCheck);
120 - break;
121 - default:
122 - throw new XmlException();
123 - }
124 - break;
125 - case XmlNodeType.EndElement:
126 - done = true;
127 - break;
128 - }
129 - }
130 -
131 - if (!done)
132 - {
133 - throw new XmlException();
134 - }
135 - }
136 -
137 - return pdb;
138 - }
139 -
140 - /// <summary>
141 - /// Persists a pdb in an XML format.
142 - /// </summary>
143 - /// <param name="writer">XmlWriter where the Pdb should persist itself as XML.</param>
144 - internal void Write(XmlWriter writer)
145 - {
146 - writer.WriteStartElement("wixPdb", XmlNamespaceUri);
147 -
148 - writer.WriteAttributeString("version", Pdb.CurrentVersion.ToString());
149 -
150 - this.Output.Write(writer);
151 -
152 - writer.WriteEndElement();
153 - }
154 - }
155 -}
src/WixToolset.Data/WindowsInstaller/Row.cs
+1 -1
@@ -354,7 +354,7 @@ namespace WixToolset.Data.WindowsInstaller
354 /// <param name="writer">XmlWriter where the Row should persist itself as XML.</param>
355 internal void Write(XmlWriter writer)
356 {
357 - writer.WriteStartElement("row", Intermediate.XmlNamespaceUri);
357 + writer.WriteStartElement("row", Output.XmlNamespaceUri);
358
359 if (RowOperation.None != this.Operation)
360 {
src/WixToolset.Data/WindowsInstaller/Table.cs
+1 -1
@@ -291,7 +291,7 @@ namespace WixToolset.Data.WindowsInstaller
291 throw new ArgumentNullException("writer");
292 }
293
294 - writer.WriteStartElement("table", Intermediate.XmlNamespaceUri);
294 + writer.WriteStartElement("table", Output.XmlNamespaceUri);
295 writer.WriteAttributeString("name", this.Name);
296
297 if (TableOperation.None != this.Operation)
src/WixToolset.Data/WindowsInstaller/TableDefinitionCollection.cs
+1 -1
@@ -227,7 +227,7 @@ namespace WixToolset.Data.WindowsInstaller
227 {
228 writer.WriteStartElement("tableDefinitions", XmlNamespaceUri);
229
230 - foreach (TableDefinition tableDefinition in this.collection.Values.OrderBy(t => t.Name))
230 + foreach (var tableDefinition in this.collection.Values.OrderBy(t => t.Name))
231 {
232 tableDefinition.Write(writer);
233 }
src/WixToolset.Data/WixCorruptFileException.cs
+2 -2
@@ -9,7 +9,7 @@ namespace WixToolset.Data
9 /// </summary>
10 public class WixCorruptFileException : WixException
11 {
12 - public WixCorruptFileException(string path, FileFormat format, Exception innerException = null)
12 + public WixCorruptFileException(string path, string format, Exception innerException = null)
13 : base(ErrorMessages.CorruptFileFormat(path, format), innerException)
14 {
15 this.Path = path;
@@ -19,7 +19,7 @@ namespace WixToolset.Data
19 /// <summary>
20 /// Gets the actual file format found in the file.
21 /// </summary>
22 - public FileFormat FileFormat { get; }
22 + public string FileFormat { get; }
23
24 /// <summary>
25 /// Gets the path to the file with unexpected format.
src/WixToolset.Data/WixOutput.cs new
+233
@@ -0,0 +1,233 @@
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 ZipArchive archive;
17 + private bool disposed;
18 +
19 + private WixOutput(Uri uri, ZipArchive archive)
20 + {
21 + this.Uri = uri;
22 + this.archive = archive;
23 + }
24 +
25 + public Uri Uri { get; }
26 +
27 + /// <summary>
28 + /// Creates a new file structure in memory.
29 + /// </summary>
30 + /// <returns>Newly created <c>WixOutput</c>.</returns>
31 + public static WixOutput Create()
32 + {
33 + var uri = new Uri("memorystream:");
34 +
35 + var stream = new MemoryStream();
36 +
37 + return WixOutput.Create(uri, stream);
38 + }
39 +
40 + /// <summary>
41 + /// Creates a new file structure on disk.
42 + /// </summary>
43 + /// <param name="path">Path to write file structure to.</param>
44 + /// <returns>Newly created <c>WixOutput</c>.</returns>
45 + public static WixOutput Create(string path)
46 + {
47 + var uri = new Uri(Path.GetFullPath(path));
48 +
49 + var stream = File.Create(path);
50 +
51 + return WixOutput.Create(uri, stream);
52 + }
53 +
54 + /// <summary>
55 + /// Creates a new file structure.
56 + /// </summary>
57 + /// <param name="stream">Stream to write the file structure to.</param>
58 + /// <param name="embedFilePaths">Paths to files to embedd in the file structure.</param>
59 + /// <returns>Newly created <c>WixOutput</c>.</returns>
60 + public static WixOutput Create(Uri uri, Stream stream)
61 + {
62 + var archive = new ZipArchive(stream, ZipArchiveMode.Update);
63 +
64 + return new WixOutput(uri, archive);
65 + }
66 +
67 + /// <summary>
68 + /// Loads a wixout from a path on disk.
69 + /// </summary>
70 + /// <param name="path">Path to wixout file saved on disk.</param>
71 + /// <returns>Loaded created <c>WixOutput</c>.</returns>
72 + public static WixOutput Read(string path)
73 + {
74 + var uri = new Uri(Path.GetFullPath(path));
75 +
76 + var stream = File.OpenRead(path);
77 +
78 + return Read(uri, stream);
79 + }
80 +
81 + /// <summary>
82 + /// Loads a wixout from a path on disk or embedded resource in assembly.
83 + /// </summary>
84 + /// <param name="baseUri">Uri with local path to wixout file saved on disk or embedded resource in assembly.</param>
85 + /// <returns>Loaded created <c>WixOutput</c>.</returns>
86 + public static WixOutput Read(Uri baseUri)
87 + {
88 + // If the embedded files are stored in an assembly resource stream (usually
89 + // a .wixlib embedded in a WixExtension).
90 + if ("embeddedresource" == baseUri.Scheme)
91 + {
92 + var assemblyPath = Path.GetFullPath(baseUri.LocalPath);
93 + var resourceName = baseUri.Fragment.TrimStart('#');
94 +
95 + var assembly = Assembly.LoadFile(assemblyPath);
96 + return WixOutput.Read(assembly, resourceName);
97 + }
98 + else // normal file (usually a binary .wixlib on disk).
99 + {
100 + var stream = File.OpenRead(baseUri.LocalPath);
101 + return WixOutput.Read(baseUri, stream);
102 + }
103 + }
104 +
105 + /// <summary>
106 + /// Loads a wixout from a assembly resource stream.
107 + /// </summary>
108 + /// <param name="path">Path to wixout file saved on disk.</param>
109 + public static WixOutput Read(Assembly assembly, string resourceName)
110 + {
111 + var resourceStream = assembly.GetManifestResourceStream(resourceName);
112 +
113 + var uriBuilder = new UriBuilder(assembly.CodeBase)
114 + {
115 + Scheme = "embeddedresource",
116 + Fragment = resourceName
117 + };
118 +
119 + return Read(uriBuilder.Uri, resourceStream);
120 + }
121 +
122 + /// <summary>
123 + /// Reads a file structure from an open stream.
124 + /// </summary>
125 + /// <param name="stream">Stream to read from.</param>
126 + /// <returns>Loaded created <c>WixOutput</c>.</returns>
127 + public static WixOutput Read(Uri uri, Stream stream, bool leaveOpen = false)
128 + {
129 + try
130 + {
131 + var archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen);
132 +
133 + return new WixOutput(uri, archive);
134 + }
135 + catch (InvalidDataException)
136 + {
137 + throw new WixException(ErrorMessages.CorruptFileFormat(uri.AbsoluteUri, "wixout"));
138 + }
139 + }
140 +
141 + /// <summary>
142 + /// Extracts an embedded file.
143 + /// </summary>
144 + /// <param name="embeddedId">Id to the file to extract.</param>
145 + /// <param name="outputPath">Path to write the extracted file to.</param>
146 + public void ExtractEmbeddedFile(string embeddedId, string outputPath)
147 + {
148 + var entry = this.archive.GetEntry(embeddedId);
149 +
150 + if (entry == null)
151 + {
152 + throw new ArgumentOutOfRangeException(nameof(embeddedId));
153 + }
154 +
155 + var folder = Path.GetDirectoryName(outputPath);
156 +
157 + Directory.CreateDirectory(folder);
158 +
159 + entry.ExtractToFile(outputPath);
160 + }
161 +
162 + /// <summary>
163 + /// Creates a data stream in the wixout.
164 + /// </summary>
165 + /// <returns>Stream to the data of the file.</returns>
166 + public Stream CreateDataStream(string name)
167 + {
168 + var entry = this.archive.CreateEntry(name);
169 +
170 + return entry.Open();
171 + }
172 +
173 + public void ImportDataStream(string name, string path)
174 + {
175 + this.archive.CreateEntryFromFile(path, name, System.IO.Compression.CompressionLevel.Optimal);
176 + }
177 +
178 + /// <summary>
179 + /// Gets a non-closing stream to the data of the file.
180 + /// </summary>
181 + /// <returns>Stream to the data of the file.</returns>
182 + public Stream GetDataStream(string name)
183 + {
184 + var entry = this.archive.GetEntry(name);
185 +
186 + return entry.Open();
187 + }
188 +
189 + /// <summary>
190 + /// Gets the data of the file as a string.
191 + /// </summary>
192 + /// <returns>String contents data of the file.</returns>
193 + public string GetData(string name)
194 + {
195 + var entry = this.archive.GetEntry(name);
196 +
197 + var bytes = new byte[entry.Length];
198 +
199 + using (var stream = entry.Open())
200 + {
201 + stream.Read(bytes, 0, bytes.Length);
202 + }
203 +
204 + return Encoding.UTF8.GetString(bytes);
205 + }
206 +
207 + /// <summary>
208 + /// Disposes of the internal state of the file structure.
209 + /// </summary>
210 + public void Dispose()
211 + {
212 + this.Dispose(true);
213 + GC.SuppressFinalize(this);
214 + }
215 +
216 + /// <summary>
217 + /// Disposes of the internsl state of the file structure.
218 + /// </summary>
219 + /// <param name="disposing">True if disposing.</param>
220 + protected virtual void Dispose(bool disposing)
221 + {
222 + if (!this.disposed)
223 + {
224 + if (disposing)
225 + {
226 + this.archive?.Dispose();
227 + }
228 + }
229 +
230 + this.disposed = true;
231 + }
232 + }
233 +}
src/WixToolset.Data/WixToolset.Data.csproj
+4
@@ -11,6 +11,10 @@
11 <PublishRepositoryUrl>true</PublishRepositoryUrl>
12 </PropertyGroup>
13
14 + <ItemGroup>
15 + <PackageReference Include="System.IO.Compression" Version="4.3.0" />
16 + </ItemGroup>
17 +
18 <ItemGroup>
19 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-18618-05" PrivateAssets="All" />
20 <PackageReference Include="Nerdbank.GitVersioning" Version="2.1.65" PrivateAssets="all" />
src/WixToolset.Data/WixUnexpectedFileFormatException.cs
+3 -3
@@ -9,7 +9,7 @@ namespace WixToolset.Data
9 /// </summary>
10 public class WixUnexpectedFileFormatException : WixException
11 {
12 - public WixUnexpectedFileFormatException(string path, FileFormat expectedFormat, FileFormat format, Exception innerException = null)
12 + public WixUnexpectedFileFormatException(string path, string expectedFormat, string format, Exception innerException = null)
13 : base(ErrorMessages.UnexpectedFileFormat(path, expectedFormat, format), innerException)
14 {
15 this.Path = path;
@@ -20,12 +20,12 @@ namespace WixToolset.Data
20 /// <summary>
21 /// Gets the expected file format.
22 /// </summary>
23 - public FileFormat ExpectedFileFormat { get; }
23 + public string ExpectedFileFormat { get; }
24
25 /// <summary>
26 /// Gets the actual file format found in the file.
27 /// </summary>
28 - public FileFormat FileFormat { get; }
28 + public string FileFormat { get; }
29
30 /// <summary>
31 /// Gets the path to the file with unexpected format.
src/test/WixToolsetTest.Data/SerializeFixture.cs
+7 -7
@@ -26,7 +26,7 @@ namespace WixToolsetTest.Data
26 Location = ComponentLocation.Either,
27 });
28
29 - var intermediate = new Intermediate("TestIntermediate", new[] { section }, null, null);
29 + var intermediate = new Intermediate("TestIntermediate", new[] { section }, null);
30
31 var path = Path.GetTempFileName();
32 intermediate.Save(path);
@@ -64,7 +64,7 @@ namespace WixToolsetTest.Data
64
65 section.Tuples.Add(tuple);
66
67 - var intermediate = new Intermediate("TestIntermediate", new[] { section }, null, null);
67 + var intermediate = new Intermediate("TestIntermediate", new[] { section }, null);
68
69 var path = Path.GetTempFileName();
70 try
@@ -108,7 +108,7 @@ namespace WixToolsetTest.Data
108 var section = new IntermediateSection("test", SectionType.Product, 65001);
109 section.Tuples.Add(tuple);
110
111 - var intermediate1 = new Intermediate("TestIntermediate", new[] { section }, null, null);
111 + var intermediate1 = new Intermediate("TestIntermediate", new[] { section }, null);
112
113 // Intermediate #2
114 var fieldDefs2 = new[]
@@ -130,7 +130,7 @@ namespace WixToolsetTest.Data
130 var section2 = new IntermediateSection("test2", SectionType.Fragment, 65001);
131 section2.Tuples.Add(tuple2);
132
133 - var intermediate2 = new Intermediate("TestIntermediate2", new[] { section2 }, null, null);
133 + var intermediate2 = new Intermediate("TestIntermediate2", new[] { section2 }, null);
134
135 // Save
136 var path1 = Path.GetTempFileName();
@@ -191,7 +191,7 @@ namespace WixToolsetTest.Data
191 var section = new IntermediateSection("test", SectionType.Product, 65001);
192 section.Tuples.Add(tuple);
193
194 - var intermediate1 = new Intermediate("TestIntermediate", new[] { section }, null, null);
194 + var intermediate1 = new Intermediate("TestIntermediate", new[] { section }, null);
195
196 // Intermediate #2
197 var fieldDefs2 = new[]
@@ -219,7 +219,7 @@ namespace WixToolsetTest.Data
219 var section2 = new IntermediateSection("test2", SectionType.Fragment, 65001);
220 section2.Tuples.Add(tuple2);
221
222 - var intermediate2 = new Intermediate("TestIntermediate2", new[] { section2 }, null, null);
222 + var intermediate2 = new Intermediate("TestIntermediate2", new[] { section2 }, null);
223
224 // Save
225 var path1 = Path.GetTempFileName();
@@ -290,7 +290,7 @@ namespace WixToolsetTest.Data
290 Location = ComponentLocation.Either,
291 });
292
293 - var intermediate = new Intermediate("TestIntermediate", new[] { section }, localizations.ToDictionary(l => l.Culture), null);
293 + var intermediate = new Intermediate("TestIntermediate", new[] { section }, localizations.ToDictionary(l => l.Culture));
294
295 var path = Path.GetTempFileName();
296 try