aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs159
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs32
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs14
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs1
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs38
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs15
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/CabFixture.cs71
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs4
8 files changed, 208 insertions, 126 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs
index 1d677a70..b75956b4 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs
@@ -35,7 +35,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
35 35
36 private bool FilesCompressed { get; } 36 private bool FilesCompressed { get; }
37 37
38 public string CabinetNameTemplate { private get; set; } 38 private string CabinetNameTemplate { get; set; }
39 39
40 /// <summary> 40 /// <summary>
41 /// Gets cabinets with their file rows. 41 /// Gets cabinets with their file rows.
@@ -43,11 +43,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
43 public Dictionary<MediaTuple, IEnumerable<FileFacade>> FileFacadesByCabinetMedia { get; private set; } 43 public Dictionary<MediaTuple, IEnumerable<FileFacade>> FileFacadesByCabinetMedia { get; private set; }
44 44
45 /// <summary> 45 /// <summary>
46 /// Get media rows.
47 /// </summary>
48 public Dictionary<int, MediaTuple> MediaRows { get; private set; }
49
50 /// <summary>
51 /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no. 46 /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no.
52 /// This contains all the files when Package element is marked with compression=no 47 /// This contains all the files when Package element is marked with compression=no
53 /// </summary> 48 /// </summary>
@@ -55,17 +50,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
55 50
56 public void Execute() 51 public void Execute()
57 { 52 {
58 var filesByCabinetMedia = new Dictionary<MediaTuple, List<FileFacade>>(); 53 var mediaTuples = this.Section.Tuples.OfType<MediaTuple>().ToList();
59 54 var mediaTemplateTuples = this.Section.Tuples.OfType<WixMediaTemplateTuple>().ToList();
60 var mediaRows = new Dictionary<int, MediaTuple>();
61
62 var uncompressedFiles = new List<FileFacade>();
63
64 var mediaTable = this.Section.Tuples.OfType<MediaTuple>().ToList();
65 var mediaTemplateTable = this.Section.Tuples.OfType<WixMediaTemplateTuple>().ToList();
66 55
67 // If both tables are authored, it is an error. 56 // If both tuples are authored, it is an error.
68 if (mediaTemplateTable.Count > 0 && mediaTable.Count > 1) 57 if (mediaTemplateTuples.Count > 0 && mediaTuples.Count > 1)
69 { 58 {
70 throw new WixException(ErrorMessages.MediaTableCollision(null)); 59 throw new WixException(ErrorMessages.MediaTableCollision(null));
71 } 60 }
@@ -78,34 +67,44 @@ namespace WixToolset.Core.WindowsInstaller.Bind
78 Cabinet = "#MergeModule.CABinet", 67 Cabinet = "#MergeModule.CABinet",
79 }); 68 });
80 69
81 filesByCabinetMedia.Add(mergeModuleMediaTuple, new List<FileFacade>(this.FileFacades)); 70 this.FileFacadesByCabinetMedia = new Dictionary<MediaTuple, IEnumerable<FileFacade>>
71 {
72 { mergeModuleMediaTuple, this.FileFacades }
73 };
74
75 this.UncompressedFileFacades = Array.Empty<FileFacade>();
82 } 76 }
83 else if (mediaTemplateTable.Count == 0) 77 else if (mediaTemplateTuples.Count == 0)
84 { 78 {
85 this.ManuallyAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles); 79 var filesByCabinetMedia = new Dictionary<MediaTuple, List<FileFacade>>();
80
81 var uncompressedFiles = new List<FileFacade>();
82
83 this.ManuallyAssignFiles(mediaTuples, filesByCabinetMedia, uncompressedFiles);
84
85 this.FileFacadesByCabinetMedia = filesByCabinetMedia.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable<FileFacade>)kvp.Value);
86
87 this.UncompressedFileFacades = uncompressedFiles;
86 } 88 }
87 else 89 else
88 { 90 {
89 this.AutoAssignFiles(mediaTable, filesByCabinetMedia, mediaRows, uncompressedFiles); 91 var filesByCabinetMedia = new Dictionary<MediaTuple, List<FileFacade>>();
90 }
91 92
92 this.FileFacadesByCabinetMedia = new Dictionary<MediaTuple, IEnumerable<FileFacade>>(); 93 var uncompressedFiles = new List<FileFacade>();
93 94
94 foreach (var mediaRowWithFiles in filesByCabinetMedia) 95 this.AutoAssignFiles(mediaTuples, filesByCabinetMedia, uncompressedFiles);
95 {
96 this.FileFacadesByCabinetMedia.Add(mediaRowWithFiles.Key, mediaRowWithFiles.Value);
97 }
98 96
99 this.MediaRows = mediaRows; 97 this.FileFacadesByCabinetMedia = filesByCabinetMedia.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable<FileFacade>)kvp.Value);
100 98
101 this.UncompressedFileFacades = uncompressedFiles; 99 this.UncompressedFileFacades = uncompressedFiles;
100 }
102 } 101 }
103 102
104 /// <summary> 103 /// <summary>
105 /// Assign files to cabinets based on MediaTemplate authoring. 104 /// Assign files to cabinets based on MediaTemplate authoring.
106 /// </summary> 105 /// </summary>
107 /// <param name="fileFacades">FileRowCollection</param> 106 /// <param name="fileFacades">FileRowCollection</param>
108 private void AutoAssignFiles(List<MediaTuple> mediaTable, Dictionary<MediaTuple, List<FileFacade>> filesByCabinetMedia, Dictionary<int, MediaTuple> mediaRows, List<FileFacade> uncompressedFiles) 107 private void AutoAssignFiles(List<MediaTuple> mediaTable, Dictionary<MediaTuple, List<FileFacade>> filesByCabinetMedia, List<FileFacade> uncompressedFiles)
109 { 108 {
110 const int MaxCabIndex = 999; 109 const int MaxCabIndex = 999;
111 110
@@ -158,6 +157,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
158 throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB)); 157 throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB));
159 } 158 }
160 159
160 var mediaTuplesByDiskId = new Dictionary<int, MediaTuple>();
161
161 foreach (var facade in this.FileFacades) 162 foreach (var facade in this.FileFacades)
162 { 163 {
163 // When building a product, if the current file is not to be compressed or if 164 // When building a product, if the current file is not to be compressed or if
@@ -171,44 +172,38 @@ namespace WixToolset.Core.WindowsInstaller.Bind
171 if (currentCabIndex == MaxCabIndex) 172 if (currentCabIndex == MaxCabIndex)
172 { 173 {
173 // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore. 174 // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore.
174 var cabinetFiles = filesByCabinetMedia[currentMediaRow];
175 facade.DiskId = currentCabIndex;
176 cabinetFiles.Add(facade);
177 continue;
178 }
179
180 // Update current cab size.
181 currentPreCabSize += (ulong)facade.FileSize;
182
183 if (currentPreCabSize > maxPreCabSizeInBytes)
184 {
185 // Overflow due to current file
186 currentMediaRow = this.AddMediaRow(mediaTemplateRow, ++currentCabIndex);
187 mediaRows.Add(currentMediaRow.DiskId, currentMediaRow);
188 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
189
190 var cabinetFileRows = filesByCabinetMedia[currentMediaRow];
191 facade.DiskId = currentCabIndex;
192 cabinetFileRows.Add(facade);
193 // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize
194 currentPreCabSize = (ulong)facade.FileSize;
195 } 175 }
196 else 176 else
197 { 177 {
198 // File fits in the current cab. 178 // Update current cab size.
199 if (currentMediaRow == null) 179 currentPreCabSize += (ulong)facade.FileSize;
180
181 // Overflow due to current file
182 if (currentPreCabSize > maxPreCabSizeInBytes)
200 { 183 {
201 // Create new cab and MediaRow 184 currentMediaRow = this.AddMediaTuple(mediaTemplateRow, ++currentCabIndex);
202 currentMediaRow = this.AddMediaRow(mediaTemplateRow, ++currentCabIndex); 185 mediaTuplesByDiskId.Add(currentMediaRow.DiskId, currentMediaRow);
203 mediaRows.Add(currentMediaRow.DiskId, currentMediaRow);
204 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>()); 186 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
205 }
206 187
207 // Associate current file with current cab. 188 // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize
208 var cabinetFiles = filesByCabinetMedia[currentMediaRow]; 189 currentPreCabSize = (ulong)facade.FileSize;
209 facade.DiskId = currentCabIndex; 190 }
210 cabinetFiles.Add(facade); 191 else // file fits in the current cab.
192 {
193 if (currentMediaRow == null)
194 {
195 // Create new cab and MediaRow
196 currentMediaRow = this.AddMediaTuple(mediaTemplateRow, ++currentCabIndex);
197 mediaTuplesByDiskId.Add(currentMediaRow.DiskId, currentMediaRow);
198 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
199 }
200 }
211 } 201 }
202
203 // Associate current file with current cab.
204 var cabinetFiles = filesByCabinetMedia[currentMediaRow];
205 facade.DiskId = currentCabIndex;
206 cabinetFiles.Add(facade);
212 } 207 }
213 208
214 // If there are uncompressed files and no MediaRow, create a default one. 209 // If there are uncompressed files and no MediaRow, create a default one.
@@ -219,51 +214,45 @@ namespace WixToolset.Core.WindowsInstaller.Bind
219 DiskId = 1, 214 DiskId = 1,
220 }); 215 });
221 216
222 mediaRows.Add(1, defaultMediaRow); 217 mediaTuplesByDiskId.Add(1, defaultMediaRow);
223 } 218 }
224 } 219 }
225 220
226 /// <summary> 221 /// <summary>
227 /// Assign files to cabinets based on Media authoring. 222 /// Assign files to cabinets based on Media authoring.
228 /// </summary> 223 /// </summary>
229 /// <param name="mediaTable"></param> 224 private void ManuallyAssignFiles(List<MediaTuple> mediaTuples, Dictionary<MediaTuple, List<FileFacade>> filesByCabinetMedia, List<FileFacade> uncompressedFiles)
230 /// <param name="fileFacades"></param>
231 private void ManuallyAssignFiles(List<MediaTuple> mediaTable, IEnumerable<FileFacade> fileFacades, Dictionary<MediaTuple, List<FileFacade>> filesByCabinetMedia, Dictionary<int, MediaTuple> mediaRows, List<FileFacade> uncompressedFiles)
232 { 225 {
233 if (mediaTable.Any()) 226 var mediaTuplesByDiskId = new Dictionary<int, MediaTuple>();
227
228 if (mediaTuples.Any())
234 { 229 {
235 var cabinetMediaRows = new Dictionary<string, MediaTuple>(StringComparer.OrdinalIgnoreCase); 230 var cabinetMediaTuples = new Dictionary<string, MediaTuple>(StringComparer.OrdinalIgnoreCase);
236 foreach (var mediaRow in mediaTable) 231 foreach (var mediaTuple in mediaTuples)
237 { 232 {
238 // If the Media row has a cabinet, make sure it is unique across all Media rows. 233 // If the Media row has a cabinet, make sure it is unique across all Media rows.
239 if (!String.IsNullOrEmpty(mediaRow.Cabinet)) 234 if (!String.IsNullOrEmpty(mediaTuple.Cabinet))
240 { 235 {
241 if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out var existingRow)) 236 if (cabinetMediaTuples.TryGetValue(mediaTuple.Cabinet, out var existingRow))
242 { 237 {
243 this.Messaging.Write(ErrorMessages.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet)); 238 this.Messaging.Write(ErrorMessages.DuplicateCabinetName(mediaTuple.SourceLineNumbers, mediaTuple.Cabinet));
244 this.Messaging.Write(ErrorMessages.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet)); 239 this.Messaging.Write(ErrorMessages.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet));
245 } 240 }
246 else 241 else
247 { 242 {
248 cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow); 243 cabinetMediaTuples.Add(mediaTuple.Cabinet, mediaTuple);
249 } 244 }
250 }
251 245
252 mediaRows.Add(mediaRow.DiskId, mediaRow); 246 filesByCabinetMedia.Add(mediaTuple, new List<FileFacade>());
253 } 247 }
254 }
255 248
256 foreach (var mediaRow in mediaRows.Values) 249 mediaTuplesByDiskId.Add(mediaTuple.DiskId, mediaTuple);
257 {
258 if (null != mediaRow.Cabinet)
259 {
260 filesByCabinetMedia.Add(mediaRow, new List<FileFacade>());
261 } 250 }
262 } 251 }
263 252
264 foreach (var facade in fileFacades) 253 foreach (var facade in this.FileFacades)
265 { 254 {
266 if (!mediaRows.TryGetValue(facade.DiskId, out var mediaRow)) 255 if (!mediaTuplesByDiskId.TryGetValue(facade.DiskId, out var mediaTuple))
267 { 256 {
268 this.Messaging.Write(ErrorMessages.MissingMedia(facade.SourceLineNumber, facade.DiskId)); 257 this.Messaging.Write(ErrorMessages.MissingMedia(facade.SourceLineNumber, facade.DiskId));
269 continue; 258 continue;
@@ -279,7 +268,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
279 } 268 }
280 else // file is marked compressed. 269 else // file is marked compressed.
281 { 270 {
282 if (filesByCabinetMedia.TryGetValue(mediaRow, out var cabinetFiles)) 271 if (filesByCabinetMedia.TryGetValue(mediaTuple, out var cabinetFiles))
283 { 272 {
284 cabinetFiles.Add(facade); 273 cabinetFiles.Add(facade);
285 } 274 }
@@ -292,12 +281,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
292 } 281 }
293 282
294 /// <summary> 283 /// <summary>
295 /// Adds a row to the media table with cab name template filled in. 284 /// Adds a tuple to the section with cab name template filled in.
296 /// </summary> 285 /// </summary>
297 /// <param name="mediaTable"></param> 286 /// <param name="mediaTable"></param>
298 /// <param name="cabIndex"></param> 287 /// <param name="cabIndex"></param>
299 /// <returns></returns> 288 /// <returns></returns>
300 private MediaTuple AddMediaRow(WixMediaTemplateTuple mediaTemplateTuple, int cabIndex) 289 private MediaTuple AddMediaTuple(WixMediaTemplateTuple mediaTemplateTuple, int cabIndex)
301 { 290 {
302 return this.Section.AddTuple(new MediaTuple(mediaTemplateTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, cabIndex)) 291 return this.Section.AddTuple(new MediaTuple(mediaTemplateTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, cabIndex))
303 { 292 {
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
index 6c2968ec..da92be69 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
@@ -281,19 +281,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
281 command.Execute(); 281 command.Execute();
282 } 282 }
283 283
284 // Assign files to media.
285 Dictionary<int, MediaTuple> assignedMediaRows;
286 Dictionary<MediaTuple, IEnumerable<FileFacade>> filesByCabinetMedia;
287 IEnumerable<FileFacade> uncompressedFiles;
288 {
289 var command = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed);
290 command.Execute();
291
292 assignedMediaRows = command.MediaRows;
293 filesByCabinetMedia = command.FileFacadesByCabinetMedia;
294 uncompressedFiles = command.UncompressedFileFacades;
295 }
296
297 // stop processing if an error previously occurred 284 // stop processing if an error previously occurred
298 if (this.Messaging.EncounteredError) 285 if (this.Messaging.EncounteredError)
299 { 286 {
@@ -366,10 +353,23 @@ namespace WixToolset.Core.WindowsInstaller.Bind
366 command.Execute(); 353 command.Execute();
367 } 354 }
368 355
369 // Update file sequence. 356 // Assign files to media and update file sequences.
357 Dictionary<MediaTuple, IEnumerable<FileFacade>> filesByCabinetMedia;
358 IEnumerable<FileFacade> uncompressedFiles;
370 { 359 {
371 var command = new UpdateMediaSequencesCommand(section, fileFacades); 360 var order = new OptimizeFileFacadesOrderCommand(fileFacades);
372 command.Execute(); 361 order.Execute();
362
363 fileFacades = order.FileFacades;
364
365 var assign = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed);
366 assign.Execute();
367
368 filesByCabinetMedia = assign.FileFacadesByCabinetMedia;
369 uncompressedFiles = assign.UncompressedFileFacades;
370
371 var update = new UpdateMediaSequencesCommand(section, fileFacades);
372 update.Execute();
373 } 373 }
374 374
375 // stop processing if an error previously occurred 375 // stop processing if an error previously occurred
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs
index 486ee67a..dce89f78 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs
@@ -7,7 +7,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
7 using System.IO; 7 using System.IO;
8 using System.Linq; 8 using System.Linq;
9 using System.Threading; 9 using System.Threading;
10 using WixToolset.Core.Bind;
11 using WixToolset.Core.Native; 10 using WixToolset.Core.Native;
12 using WixToolset.Data; 11 using WixToolset.Data;
13 using WixToolset.Extensibility.Services; 12 using WixToolset.Extensibility.Services;
@@ -18,12 +17,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
18 /// </summary> 17 /// </summary>
19 internal sealed class CabinetBuilder 18 internal sealed class CabinetBuilder
20 { 19 {
21 private Queue cabinetWorkItems; 20 private readonly object lockObject = new object();
22 private object lockObject; 21
22 private readonly Queue cabinetWorkItems;
23 private int threadCount; 23 private int threadCount;
24 24
25 // Address of Binder's callback function for Cabinet Splitting 25 // Address of Binder's callback function for Cabinet Splitting
26 private IntPtr newCabNamesCallBackAddress; 26 private readonly IntPtr newCabNamesCallBackAddress;
27 27
28 /// <summary> 28 /// <summary>
29 /// Instantiate a new CabinetBuilder. 29 /// Instantiate a new CabinetBuilder.
@@ -38,7 +38,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
38 } 38 }
39 39
40 this.cabinetWorkItems = new Queue(); 40 this.cabinetWorkItems = new Queue();
41 this.lockObject = new object();
42 this.Messaging = messaging; 41 this.Messaging = messaging;
43 this.threadCount = threadCount; 42 this.threadCount = threadCount;
44 43
@@ -56,10 +55,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
56 /// Enqueues a CabinetWorkItem to the queue. 55 /// Enqueues a CabinetWorkItem to the queue.
57 /// </summary> 56 /// </summary>
58 /// <param name="cabinetWorkItem">cabinet work item</param> 57 /// <param name="cabinetWorkItem">cabinet work item</param>
59 public void Enqueue(CabinetWorkItem cabinetWorkItem) 58 public void Enqueue(CabinetWorkItem cabinetWorkItem) => this.cabinetWorkItems.Enqueue(cabinetWorkItem);
60 {
61 this.cabinetWorkItems.Enqueue(cabinetWorkItem);
62 }
63 59
64 /// <summary> 60 /// <summary>
65 /// Create the queued cabinets. 61 /// Create the queued cabinets.
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
index de357e53..9741fcd9 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
@@ -171,7 +171,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
171 return cabbingThreadCount; 171 return cabbingThreadCount;
172 } 172 }
173 173
174
175 /// <summary> 174 /// <summary>
176 /// Creates a work item to create a cabinet. 175 /// Creates a work item to create a cabinet.
177 /// </summary> 176 /// </summary>
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs
new file mode 100644
index 00000000..6943d345
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs
@@ -0,0 +1,38 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Core.Bind;
8
9 internal class OptimizeFileFacadesOrderCommand
10 {
11 public OptimizeFileFacadesOrderCommand(List<FileFacade> fileFacades)
12 {
13 this.FileFacades = fileFacades;
14 }
15
16 public List<FileFacade> FileFacades { get; private set; }
17
18 public List<FileFacade> Execute()
19 {
20 this.FileFacades.Sort(FileFacadeOptimizer.Instance);
21
22 return this.FileFacades;
23 }
24
25 private class FileFacadeOptimizer : IComparer<FileFacade>
26 {
27 public static readonly FileFacadeOptimizer Instance = new FileFacadeOptimizer();
28
29 public int Compare(FileFacade x, FileFacade y)
30 {
31 // TODO: Sort these facades even smarter by directory path and component id
32 // and maybe file size or file extension and other creative ideas to
33 // get optimal install speed out of MSI.
34 return String.Compare(x.ComponentRef, y.ComponentRef, StringComparison.Ordinal);
35 }
36 }
37 }
38}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
index 9aab7b98..bf28b279 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
@@ -29,9 +29,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
29 { 29 {
30 var lastSequence = 0; 30 var lastSequence = 0;
31 31
32 // Order by Component to group the files by directory. 32 foreach (var facade in this.FileFacades)
33 var optimized = this.OptimizedFileFacades();
34 foreach (var facade in optimized)
35 { 33 {
36 facade.Sequence = ++lastSequence; 34 facade.Sequence = ++lastSequence;
37 } 35 }
@@ -43,8 +41,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
43 var patchGroups = new Dictionary<int, List<FileFacade>>(); 41 var patchGroups = new Dictionary<int, List<FileFacade>>();
44 42
45 // sequence the non-patch-added files 43 // sequence the non-patch-added files
46 var optimized = this.OptimizedFileFacades(); 44 foreach (var facade in this.FileFacades)
47 foreach (var facade in optimized)
48 { 45 {
49 if (null == mediaTuple) 46 if (null == mediaTuple)
50 { 47 {
@@ -108,13 +105,5 @@ namespace WixToolset.Core.WindowsInstaller.Bind
108 } 105 }
109 } 106 }
110 } 107 }
111
112 private IEnumerable<FileFacade> OptimizedFileFacades()
113 {
114 // TODO: Sort these facades even smarter by directory path and component id
115 // and maybe file size or file extension and other creative ideas to
116 // get optimal install speed out of MSI.
117 return this.FileFacades.OrderBy(f => f.ComponentRef);
118 }
119 } 108 }
120} 109}
diff --git a/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs
new file mode 100644
index 00000000..79471554
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs
@@ -0,0 +1,71 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.IO;
7 using System.Linq;
8 using WixBuildTools.TestSupport;
9 using WixToolset.Core.TestPackage;
10 using Xunit;
11
12 public class CabFixture
13 {
14 [Fact]
15 public void CabinetFilesSequencedCorrectly()
16 {
17 var folder = TestData.Get(@"TestData\MultiFileCompressed");
18
19 using (var fs = new DisposableFileSystem())
20 {
21 var baseFolder = fs.GetFolder();
22 var intermediateFolder = Path.Combine(baseFolder, "obj");
23 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
24 var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab");
25
26 var result = WixRunner.Execute(new[]
27 {
28 "build",
29 Path.Combine(folder, "Package.wxs"),
30 Path.Combine(folder, "PackageComponents.wxs"),
31 "-d", "MediaTemplateCompressionLevel",
32 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
33 "-bindpath", Path.Combine(folder, "data"),
34 "-intermediateFolder", intermediateFolder,
35 "-o", msiPath
36 });
37
38 result.AssertSuccess();
39 Assert.True(File.Exists(cabPath));
40
41 var fileTable = Query.QueryDatabase(msiPath, new[] { "File" });
42 var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList();
43
44 Assert.Equal(new[] { 1, 2 }, fileRows.Select(f => f.Sequence).ToArray());
45 Assert.Equal(new[] { "test.txt", "Notepad.exe" }, fileRows.Select(f => f.Name).ToArray());
46
47 var files = Query.GetCabinetFiles(cabPath);
48 Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray());
49 }
50 }
51
52 private class FileRow
53 {
54 public FileRow(string row)
55 {
56 row = row.Substring("File:".Length);
57
58 var split = row.Split('\t');
59 this.Id = split[0];
60 this.Name = split[2];
61 this.Sequence = Convert.ToInt32(split[7]);
62 }
63
64 public string Id { get; set; }
65
66 public string Name { get; set; }
67
68 public int Sequence { get; set; }
69 }
70 }
71}
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs
index d65a07df..82797ebe 100644
--- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs
@@ -3,10 +3,10 @@
3 <Fragment> 3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> 4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component> 5 <Component>
6 <File Source="test.txt" /> 6 <File Source="$(env.WINDIR)\Notepad.exe" />
7 </Component> 7 </Component>
8 <Component> 8 <Component>
9 <File Source="$(env.WINDIR)\Notepad.exe" /> 9 <File Source="test.txt" />
10 </Component> 10 </Component>
11 </ComponentGroup> 11 </ComponentGroup>
12 </Fragment> 12 </Fragment>