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