aboutsummaryrefslogtreecommitdiff
path: root/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs')
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs400
1 files changed, 169 insertions, 231 deletions
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
index 2516a6fa..31198837 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
@@ -4,13 +4,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO; 7 using System.IO;
9 using System.Linq; 8 using System.Linq;
10 using System.Runtime.InteropServices;
11 using WixToolset.Data; 9 using WixToolset.Data;
12 using WixToolset.Data.Symbols; 10 using WixToolset.Data.Symbols;
13 using WixToolset.Data.WindowsInstaller; 11 using WixToolset.Data.WindowsInstaller;
12 using WixToolset.Data.WindowsInstaller.Rows;
14 using WixToolset.Extensibility; 13 using WixToolset.Extensibility;
15 using WixToolset.Extensibility.Data; 14 using WixToolset.Extensibility.Data;
16 using WixToolset.Extensibility.Services; 15 using WixToolset.Extensibility.Services;
@@ -20,70 +19,63 @@ namespace WixToolset.Core.WindowsInstaller.Bind
20 /// </summary> 19 /// </summary>
21 internal class CreateCabinetsCommand 20 internal class CreateCabinetsCommand
22 { 21 {
23 public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB 22 public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB
24 public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) 23 public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
25 24
25 private readonly CabinetResolver cabinetResolver;
26 private readonly List<IFileTransfer> fileTransfers; 26 private readonly List<IFileTransfer> fileTransfers;
27
28 private readonly List<ITrackedFile> trackedFiles; 27 private readonly List<ITrackedFile> trackedFiles;
29 28
30 private readonly FileSplitCabNamesCallback newCabNamesCallBack; 29 public CreateCabinetsCommand(IServiceProvider serviceProvider, IMessaging messaging, IBackendHelper backendHelper, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtensions, IntermediateSection section, string cabCachePath, int cabbingThreadCount, string outputPath, string intermediateFolder, CompressionLevel? defaultCompressionLevel, bool compressed, string modularizationSuffix, Dictionary<MediaSymbol, IEnumerable<IFileFacade>> filesByCabinetMedia, WindowsInstallerData data, TableDefinitionCollection tableDefinitions, Func<MediaSymbol, string, string, string> resolveMedia)
31
32 private Dictionary<string, string> lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence
33
34 public CreateCabinetsCommand(IServiceProvider serviceProvider, IBackendHelper backendHelper, WixMediaTemplateSymbol mediaTemplate)
35 { 30 {
36 this.fileTransfers = new List<IFileTransfer>(); 31 this.Messaging = messaging;
37 32
38 this.trackedFiles = new List<ITrackedFile>(); 33 this.BackendHelper = backendHelper;
39
40 this.newCabNamesCallBack = this.NewCabNamesCallBack;
41 34
42 this.ServiceProvider = serviceProvider; 35 this.Section = section;
43 36
44 this.BackendHelper = backendHelper; 37 this.CabbingThreadCount = cabbingThreadCount;
45 38
46 this.MediaTemplate = mediaTemplate; 39 this.IntermediateFolder = intermediateFolder;
47 } 40 this.LayoutDirectory = Path.GetDirectoryName(outputPath);
48 41
49 private IServiceProvider ServiceProvider { get; } 42 this.DefaultCompressionLevel = defaultCompressionLevel;
43 this.ModularizationSuffix = modularizationSuffix;
44 this.FileFacadesByCabinet = filesByCabinetMedia;
50 45
51 private IBackendHelper BackendHelper { get; } 46 this.Data = data;
47 this.TableDefinitions = tableDefinitions;
52 48
53 private WixMediaTemplateSymbol MediaTemplate { get; } 49 this.ResolveMedia = resolveMedia;
54 50
55 /// <summary> 51 this.cabinetResolver = new CabinetResolver(serviceProvider, cabCachePath, backendExtensions);
56 /// Sets the number of threads to use for cabinet creation. 52 this.fileTransfers = new List<IFileTransfer>();
57 /// </summary> 53 this.trackedFiles = new List<ITrackedFile>();
58 public int CabbingThreadCount { private get; set; } 54 }
59 55
60 public string CabCachePath { private get; set; } 56 private IMessaging Messaging { get; }
61 57
62 public IMessaging Messaging { private get; set; } 58 private IBackendHelper BackendHelper { get; }
63 59
64 public string IntermediateFolder { private get; set; } 60 private IntermediateSection Section { get; }
65 61
66 /// <summary> 62 private int CabbingThreadCount { get; set; }
67 /// Sets the default compression level to use for cabinets
68 /// that don't have their compression level explicitly set.
69 /// </summary>
70 public CompressionLevel? DefaultCompressionLevel { private get; set; }
71 63
72 public IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { private get; set; } 64 private string IntermediateFolder { get; }
73 65
74 public WindowsInstallerData Data { private get; set; } 66 private string LayoutDirectory { get; }
75 67
76 public string LayoutDirectory { private get; set; } 68 private CompressionLevel? DefaultCompressionLevel { get; }
77 69
78 public bool Compressed { private get; set; } 70 private string ModularizationSuffix { get; }
79 71
80 public string ModularizationSuffix { private get; set; } 72 private Dictionary<MediaSymbol, IEnumerable<IFileFacade>> FileFacadesByCabinet { get; }
81 73
82 public Dictionary<MediaSymbol, IEnumerable<IFileFacade>> FileFacadesByCabinet { private get; set; } 74 private WindowsInstallerData Data { get; }
83 75
84 public Func<MediaSymbol, string, string, string> ResolveMedia { private get; set; } 76 private TableDefinitionCollection TableDefinitions { get; }
85 77
86 public TableDefinitionCollection TableDefinitions { private get; set; } 78 private Func<MediaSymbol, string, string, string> ResolveMedia { get; }
87 79
88 public IEnumerable<IFileTransfer> FileTransfers => this.fileTransfers; 80 public IEnumerable<IFileTransfer> FileTransfers => this.fileTransfers;
89 81
@@ -91,8 +83,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
91 83
92 public void Execute() 84 public void Execute()
93 { 85 {
94 this.lastCabinetAddedToMediaTable = new Dictionary<string, string>();
95
96 // If the cabbing thread count wasn't provided, default the number of cabbing threads to the number of processors. 86 // If the cabbing thread count wasn't provided, default the number of cabbing threads to the number of processors.
97 if (this.CabbingThreadCount <= 0) 87 if (this.CabbingThreadCount <= 0)
98 { 88 {
@@ -101,13 +91,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
101 this.Messaging.Write(VerboseMessages.SetCabbingThreadCount(this.CabbingThreadCount.ToString())); 91 this.Messaging.Write(VerboseMessages.SetCabbingThreadCount(this.CabbingThreadCount.ToString()));
102 } 92 }
103 93
104 // Send Binder object to Facilitate NewCabNamesCallBack Callback
105 var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack));
106
107 // Supply Compile MediaTemplate Attributes to Cabinet Builder
108 this.GetMediaTemplateAttributes(out var maximumCabinetSizeForLargeFileSplitting, out var maximumUncompressedMediaSize); 94 this.GetMediaTemplateAttributes(out var maximumCabinetSizeForLargeFileSplitting, out var maximumUncompressedMediaSize);
109 cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting; 95
110 cabinetBuilder.MaximumUncompressedMediaSize = maximumUncompressedMediaSize; 96 var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, maximumCabinetSizeForLargeFileSplitting, maximumUncompressedMediaSize);
111 97
112 foreach (var entry in this.FileFacadesByCabinet) 98 foreach (var entry in this.FileFacadesByCabinet)
113 { 99 {
@@ -129,12 +115,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind
129 return; 115 return;
130 } 116 }
131 117
132 // create queued cabinets with multiple threads 118 // Create queued cabinets with multiple threads.
133 cabinetBuilder.CreateQueuedCabinets(); 119 cabinetBuilder.CreateQueuedCabinets();
120
134 if (this.Messaging.EncounteredError) 121 if (this.Messaging.EncounteredError)
135 { 122 {
136 return; 123 return;
137 } 124 }
125
126 this.UpdateMediaWithSpannedCabinets(cabinetBuilder.CompletedCabinets);
138 } 127 }
139 128
140 private int CalculateCabbingThreadCount() 129 private int CalculateCabbingThreadCount()
@@ -163,7 +152,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
163 private CabinetWorkItem CreateCabinetWorkItem(WindowsInstallerData data, string cabinetDir, MediaSymbol mediaSymbol, CompressionLevel compressionLevel, IEnumerable<IFileFacade> fileFacades) 152 private CabinetWorkItem CreateCabinetWorkItem(WindowsInstallerData data, string cabinetDir, MediaSymbol mediaSymbol, CompressionLevel compressionLevel, IEnumerable<IFileFacade> fileFacades)
164 { 153 {
165 CabinetWorkItem cabinetWorkItem = null; 154 CabinetWorkItem cabinetWorkItem = null;
166 var tempCabinetFileX = Path.Combine(this.IntermediateFolder, mediaSymbol.Cabinet); 155
156 var intermediateCabinetPath = Path.Combine(this.IntermediateFolder, mediaSymbol.Cabinet);
167 157
168 // check for an empty cabinet 158 // check for an empty cabinet
169 if (!fileFacades.Any()) 159 if (!fileFacades.Any())
@@ -172,25 +162,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
172 var cabinetName = mediaSymbol.Cabinet.TrimStart('#'); 162 var cabinetName = mediaSymbol.Cabinet.TrimStart('#');
173 163
174 // If building a patch, remind them to run -p for torch. 164 // If building a patch, remind them to run -p for torch.
175 if (OutputType.Patch == data.Type) 165 this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName, OutputType.Patch == data.Type));
176 {
177 this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName, true));
178 }
179 else
180 {
181 this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName));
182 }
183 } 166 }
184 167
185 var cabinetResolver = new CabinetResolver(this.ServiceProvider, this.CabCachePath, this.BackendExtensions); 168 var resolvedCabinet = this.cabinetResolver.ResolveCabinet(intermediateCabinetPath, fileFacades);
186
187 var resolvedCabinet = cabinetResolver.ResolveCabinet(tempCabinetFileX, fileFacades);
188 169
189 // create a cabinet work item if it's not being skipped 170 // Create a cabinet work item if it's not being skipped.
190 if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption) 171 if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption)
191 { 172 {
192 // Default to the threshold for best smartcabbing (makes smallest cabinet). 173 // Default to the threshold for best smartcabbing (makes smallest cabinet).
193 cabinetWorkItem = new CabinetWorkItem(mediaSymbol.SourceLineNumbers, resolvedCabinet.Path, fileFacades, maxThreshold: 0, compressionLevel: compressionLevel, modularizationSuffix: this.ModularizationSuffix); 174 cabinetWorkItem = new CabinetWorkItem(mediaSymbol.SourceLineNumbers, mediaSymbol.DiskId, resolvedCabinet.Path, fileFacades, maxThreshold: 0, compressionLevel: compressionLevel, modularizationSuffix: this.ModularizationSuffix);
194 } 175 }
195 else // reuse the cabinet from the cabinet cache. 176 else // reuse the cabinet from the cabinet cache.
196 { 177 {
@@ -235,185 +216,26 @@ namespace WixToolset.Core.WindowsInstaller.Bind
235 return cabinetWorkItem; 216 return cabinetWorkItem;
236 } 217 }
237 218
238 //private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades)
239 //{
240 // ResolvedCabinet resolved = null;
241
242 // List<BindFileWithPath> filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList();
243
244 // foreach (var extension in this.BackendExtensions)
245 // {
246 // resolved = extension.ResolveCabinet(cabinetPath, filesWithPath);
247 // if (null != resolved)
248 // {
249 // break;
250 // }
251 // }
252
253 // return resolved;
254 //}
255
256 /// <summary>
257 /// Delegate for Cabinet Split Callback
258 /// </summary>
259 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
260 internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken);
261
262 /// <summary>
263 /// Call back to Add File Transfer for new Cab and add new Cab to Media table
264 /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe
265 /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored
266 /// </summary>
267 /// <param name="firstCabName">The name of splitting cabinet without extention e.g. "cab1".</param>
268 /// <param name="newCabinetName">The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"</param>
269 /// <param name="fileToken">The file token of the first file present in the splitting cabinet</param>
270 internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabinetName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken)
271 {
272 throw new NotImplementedException();
273#if TODO_CAB_SPANNING
274 // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads
275 var mutex = new Mutex(false, "WixCabinetSplitBinderCallback");
276 try
277 {
278 if (!mutex.WaitOne(0, false)) // Check if you can get the lock
279 {
280 // Cound not get the Lock
281 this.Messaging.Write(VerboseMessages.CabinetsSplitInParallel());
282 mutex.WaitOne(); // Wait on other thread
283 }
284
285 var firstCabinetName = firstCabName + ".cab";
286 var transferAdded = false; // Used for Error Handling
287
288 // Create File Transfer for new Cabinet using transfer of Base Cabinet
289 foreach (var transfer in this.FileTransfers)
290 {
291 if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase))
292 {
293 var newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName);
294 var newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName);
295
296 var trackSource = this.BackendHelper.TrackFile(newCabSourcePath, TrackedFileType.Intermediate, transfer.SourceLineNumbers);
297 this.trackedFiles.Add(trackSource);
298
299 var trackTarget = this.BackendHelper.TrackFile(newCabTargetPath, TrackedFileType.Final, transfer.SourceLineNumbers);
300 this.trackedFiles.Add(trackTarget);
301
302 var newTransfer = this.BackendHelper.CreateFileTransfer(trackSource.Path, trackTarget.Path, transfer.Move, transfer.SourceLineNumbers);
303 this.fileTransfers.Add(newTransfer);
304
305 transferAdded = true;
306 break;
307 }
308 }
309
310 // Check if File Transfer was added
311 if (!transferAdded)
312 {
313 throw new WixException(ErrorMessages.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName));
314 }
315
316 // Add the new Cabinets to media table using LastSequence of Base Cabinet
317 var mediaTable = this.Output.Tables["Media"];
318 var wixFileTable = this.Output.Tables["WixFile"];
319 var diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain
320 var lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain
321 var lastSplitCabinetFound = false; // Used for Error Handling
322
323 var lastCabinetOfThisSequence = String.Empty;
324 // Get the Value of Last Cabinet Added in this split Sequence from Dictionary
325 if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence))
326 {
327 // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence
328 lastCabinetOfThisSequence = firstCabinetName;
329 }
330
331 foreach (MediaRow mediaRow in mediaTable.Rows)
332 {
333 // Get details for the Last Cabinet Added in this Split Sequence
334 if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
335 {
336 lastSequenceForLastSplitCabAdded = mediaRow.LastSequence;
337 diskIDForLastSplitCabAdded = mediaRow.DiskId;
338 lastSplitCabinetFound = true;
339 }
340
341 // Check for Name Collision for the new Cabinet added
342 if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
343 {
344 // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row
345 throw new WixException(ErrorMessages.SplitCabinetNameCollision(newCabinetName, firstCabinetName));
346 }
347 }
348
349 // Check if the last Split Cabinet was found in the Media Table
350 if (!lastSplitCabinetFound)
351 {
352 throw new WixException(ErrorMessages.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence));
353 }
354
355 // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort
356 // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with
357 // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction
358 MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null);
359 newMediaRow.Cabinet = newCabinetName;
360 newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion
361 newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded;
362
363 // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique
364 foreach (MediaRow mediaRow in mediaTable.Rows)
365 {
366 // Check if this row comes after inserted row and it is not the new cabinet inserted row
367 if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
368 {
369 mediaRow.DiskId++; // Increment DiskID
370 }
371 }
372
373 // Now Increment DiskID for All files Rows so that they refer to the right Media Row
374 foreach (WixFileRow wixFileRow in wixFileTable.Rows)
375 {
376 // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet
377 // This check will work as we have only one large file in every splitting cabinet
378 // If we want to support splitting cabinet with more large files we need to update this code
379 if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase))
380 {
381 wixFileRow.DiskId++; // Increment DiskID
382 }
383 }
384
385 // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback
386 this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName;
387
388 mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails
389 }
390 finally
391 {
392 // Releasing the Mutex here
393 mutex.ReleaseMutex();
394 }
395#endif
396 }
397
398
399 /// <summary> 219 /// <summary>
400 /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides 220 /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides
401 /// </summary> 221 /// </summary>
402 private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize) 222 private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize)
403 { 223 {
404 // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size 224 var mediaTemplate = this.Section.Symbols.OfType<WixMediaTemplateSymbol>().FirstOrDefault();
405 var mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS");
406 var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
407 225
408 // Supply Compile MediaTemplate Attributes to Cabinet Builder 226 // Supply Compile MediaTemplate Attributes to Cabinet Builder
409 if (this.MediaTemplate != null) 227 if (mediaTemplate != null)
410 { 228 {
229 // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size
230 var mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS");
231 var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
232
411 // Get the Value for Max Cab Size for File Splitting 233 // Get the Value for Max Cab Size for File Splitting
412 var maxCabSizeForLargeFileInMB = 0; 234 var maxCabSizeForLargeFileInMB = 0;
413 try 235 try
414 { 236 {
415 // Override authored mcslfs value if environment variable is authored. 237 // Override authored mcslfs value if environment variable is authored.
416 maxCabSizeForLargeFileInMB = !String.IsNullOrEmpty(mcslfsString) ? Int32.Parse(mcslfsString) : this.MediaTemplate.MaximumCabinetSizeForLargeFileSplitting ?? MaxValueOfMaxCabSizeForLargeFileSplitting; 238 maxCabSizeForLargeFileInMB = !String.IsNullOrEmpty(mcslfsString) ? Int32.Parse(mcslfsString) : mediaTemplate.MaximumCabinetSizeForLargeFileSplitting ?? MaxValueOfMaxCabSizeForLargeFileSplitting;
417 239
418 var testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024; 240 var testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024;
419 maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB; 241 maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB;
@@ -431,7 +253,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
431 try 253 try
432 { 254 {
433 // Override authored mums value if environment variable is authored. 255 // Override authored mums value if environment variable is authored.
434 maxPreCompressedSizeInMB = !String.IsNullOrEmpty(mumsString) ? Int32.Parse(mumsString) : this.MediaTemplate.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize; 256 maxPreCompressedSizeInMB = !String.IsNullOrEmpty(mumsString) ? Int32.Parse(mumsString) : mediaTemplate.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize;
435 257
436 var testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024; 258 var testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024;
437 maxUncompressedMediaSize = maxPreCompressedSizeInMB; 259 maxUncompressedMediaSize = maxPreCompressedSizeInMB;
@@ -451,5 +273,121 @@ namespace WixToolset.Core.WindowsInstaller.Bind
451 maxUncompressedMediaSize = DefaultMaximumUncompressedMediaSize; 273 maxUncompressedMediaSize = DefaultMaximumUncompressedMediaSize;
452 } 274 }
453 } 275 }
276
277 private void UpdateMediaWithSpannedCabinets(IReadOnlyCollection<CompletedCabinetWorkItem> completedCabinetWorkItems)
278 {
279 var completedCabinetsSpanned = completedCabinetWorkItems.Where(c => c.CreatedCabinets.Count > 1).OrderBy(c => c.DiskId).ToList();
280
281 if (completedCabinetsSpanned.Count == 0)
282 {
283 return;
284 }
285
286 var fileTransfersByName = this.fileTransfers.ToDictionary(t => Path.GetFileName(t.Source), StringComparer.OrdinalIgnoreCase);
287 var mediaTable = this.Data.Tables["Media"];
288 var fileTable = this.Data.Tables["File"];
289 var mediaRows = mediaTable.Rows.Cast<MediaRow>().OrderBy(m => m.DiskId).ToList();
290 var fileRows = fileTable.Rows.Cast<FileRow>().OrderBy(f => f.Sequence).ToList();
291
292 var mediaRowsByOriginalDiskId = mediaRows.ToDictionary(m => m.DiskId);
293 var addedMediaRows = new List<MediaRow>();
294
295 foreach (var completedCabinetSpanned in completedCabinetsSpanned)
296 {
297 var cabinet = completedCabinetSpanned.CreatedCabinets.First();
298 var spannedCabinets = completedCabinetSpanned.CreatedCabinets.Skip(1);
299
300 if (!fileTransfersByName.TryGetValue(cabinet.CabinetName, out var transfer) ||
301 !mediaRowsByOriginalDiskId.TryGetValue(completedCabinetSpanned.DiskId, out var mediaRow))
302 {
303 throw new WixException(ErrorMessages.SplitCabinetCopyRegistrationFailed(spannedCabinets.First().CabinetName, cabinet.CabinetName));
304 }
305
306 var lastDiskId = mediaRow.DiskId;
307 var mediaRowsThatWillNeedDiskIdUpdated = mediaRows.OrderBy(m => m.DiskId).Where(m => m.DiskId > mediaRow.DiskId).ToList();
308
309 foreach (var spannedCabinet in spannedCabinets)
310 {
311 var spannedCabinetSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), spannedCabinet.CabinetName);
312 var spannedCabinetTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), spannedCabinet.CabinetName);
313
314 var trackSource = this.BackendHelper.TrackFile(spannedCabinetSourcePath, TrackedFileType.Intermediate, transfer.SourceLineNumbers);
315 this.trackedFiles.Add(trackSource);
316
317 var trackTarget = this.BackendHelper.TrackFile(spannedCabinetTargetPath, TrackedFileType.BuiltOutput, transfer.SourceLineNumbers);
318 this.trackedFiles.Add(trackTarget);
319
320 var newTransfer = this.BackendHelper.CreateFileTransfer(trackSource.Path, trackTarget.Path, transfer.Move, transfer.SourceLineNumbers);
321 this.fileTransfers.Add(newTransfer);
322
323 // FDI Extract requires DiskID of Split Cabinets to be continuous. So a new Media row must inserted just
324 // after the previous spanned cabinet according to DiskID sort order, otherwise Windows Installer will
325 // encounter Error 2350 (FDI Server Error).
326 var newMediaRow = (MediaRow)mediaTable.CreateRow(mediaRow.SourceLineNumbers);
327 newMediaRow.Cabinet = spannedCabinet.CabinetName;
328 newMediaRow.DiskId = ++lastDiskId;
329 newMediaRow.LastSequence = mediaRow.LastSequence;
330
331 addedMediaRows.Add(newMediaRow);
332 }
333
334 // Increment the DiskId for all Media rows that come after the newly inserted row to ensure that the DiskId is unique
335 // and the Media rows stay in order based on last sequence.
336 foreach (var updateMediaRow in mediaRowsThatWillNeedDiskIdUpdated)
337 {
338 updateMediaRow.DiskId = ++lastDiskId;
339 }
340 }
341
342 mediaTable.ValidateRows();
343
344 var oldDiskIdToNewDiskId = mediaRowsByOriginalDiskId.Where(originalDiskIdWithMediaRow => originalDiskIdWithMediaRow.Value.DiskId != originalDiskIdWithMediaRow.Key)
345 .ToDictionary(originalDiskIdWithMediaRow => originalDiskIdWithMediaRow.Key, originalDiskIdWithMediaRow => originalDiskIdWithMediaRow.Value.DiskId);
346
347 // Update the File row and FileSymbols so the DiskIds are correct in the WixOutput, even if this
348 // data doesn't show up in the Windows Installer database.
349 foreach (var fileRow in fileRows)
350 {
351 if (oldDiskIdToNewDiskId.TryGetValue(fileRow.DiskId, out var newDiskId))
352 {
353 fileRow.DiskId = newDiskId;
354 }
355 }
356
357 foreach (var fileSymbol in this.Section.Symbols.OfType<FileSymbol>())
358 {
359 if (fileSymbol.DiskId.HasValue && oldDiskIdToNewDiskId.TryGetValue(fileSymbol.DiskId.Value, out var newDiskId))
360 {
361 fileSymbol.DiskId = newDiskId;
362 }
363 }
364
365 // Update the MediaSymbol DiskIds to the correct DiskId. Note that the MediaSymbol Id
366 // is not changed because symbol ids are not allowed to change after they are created.
367 foreach (var mediaSymbol in this.Section.Symbols.OfType<MediaSymbol>())
368 {
369 if (oldDiskIdToNewDiskId.TryGetValue(mediaSymbol.DiskId, out var newDiskId))
370 {
371 mediaSymbol.DiskId = newDiskId;
372 }
373 }
374
375 // Now that the existing MediaSymbol DiskIds are updated, add the newly created Media rows
376 // as symbols. Notice that the new MediaSymbols do not have an Id because they very likely
377 // would conflict with MediaSymbols that had their DiskIds updated but Ids could not be updated.
378 // The newly created MediaSymbols will rename anonymous.
379 foreach (var mediaRow in addedMediaRows)
380 {
381 this.Section.AddSymbol(new MediaSymbol(mediaRow.SourceLineNumbers)
382 {
383 Cabinet = mediaRow.Cabinet,
384 DiskId = mediaRow.DiskId,
385 DiskPrompt = mediaRow.DiskPrompt,
386 LastSequence = mediaRow.LastSequence,
387 Source = mediaRow.Source,
388 VolumeLabel = mediaRow.VolumeLabel
389 });
390 }
391 }
454 } 392 }
455} 393}