aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2017-10-14 16:12:07 -0700
committerRob Mensching <rob@firegiant.com>2017-10-14 16:12:07 -0700
commitdbde9e7104b907bbbaea17e21247d8cafc8b3a4c (patch)
tree0f5fbbb6fe12c6b2e5e622a0e18ce4c5b4eb2b96 /src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs
parentfbf986eb97f68396797a89fc7d40dec07b775440 (diff)
downloadwix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.gz
wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.bz2
wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.zip
Massive refactoring to introduce the concept of IBackend
Diffstat (limited to 'src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs')
-rw-r--r--src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs489
1 files changed, 0 insertions, 489 deletions
diff --git a/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs
deleted file mode 100644
index 35c8abb4..00000000
--- a/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs
+++ /dev/null
@@ -1,489 +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
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Runtime.InteropServices;
11 using System.Threading;
12 using WixToolset.Data;
13 using WixToolset.Data.Rows;
14 using WixToolset.Extensibility;
15
16 /// <summary>
17 /// Creates cabinet files.
18 /// </summary>
19 internal class CreateCabinetsCommand : ICommand
20 {
21 private List<FileTransfer> fileTransfers;
22
23 private FileSplitCabNamesCallback newCabNamesCallBack;
24
25 private Dictionary<string, string> lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence
26
27 public CreateCabinetsCommand()
28 {
29 this.fileTransfers = new List<FileTransfer>();
30
31 this.newCabNamesCallBack = NewCabNamesCallBack;
32 }
33
34 /// <summary>
35 /// Sets the number of threads to use for cabinet creation.
36 /// </summary>
37 public int CabbingThreadCount { private get; set; }
38
39 public string TempFilesLocation { private get; set; }
40
41 /// <summary>
42 /// Sets the default compression level to use for cabinets
43 /// that don't have their compression level explicitly set.
44 /// </summary>
45 public CompressionLevel DefaultCompressionLevel { private get; set; }
46
47 public Output Output { private get; set; }
48
49 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
50
51 public string LayoutDirectory { private get; set; }
52
53 public bool Compressed { private get; set; }
54
55 public Dictionary<MediaRow, IEnumerable<FileFacade>> FileRowsByCabinet { private get; set; }
56
57 public Func<MediaRow, string, string, string> ResolveMedia { private get; set; }
58
59 public TableDefinitionCollection TableDefinitions { private get; set; }
60
61 public Table WixMediaTable { private get; set; }
62
63 public IEnumerable<FileTransfer> FileTransfers { get { return this.fileTransfers; } }
64
65 /// <param name="output">Output to generate image for.</param>
66 /// <param name="fileTransfers">Array of files to be transfered.</param>
67 /// <param name="layoutDirectory">The directory in which the image should be layed out.</param>
68 /// <param name="compressed">Flag if source image should be compressed.</param>
69 /// <returns>The uncompressed file rows.</returns>
70 public void Execute()
71 {
72 RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable);
73
74 this.lastCabinetAddedToMediaTable = new Dictionary<string, string>();
75
76 this.SetCabbingThreadCount();
77
78 // Send Binder object to Facilitate NewCabNamesCallBack Callback
79 CabinetBuilder cabinetBuilder = new CabinetBuilder(this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack));
80
81 // Supply Compile MediaTemplate Attributes to Cabinet Builder
82 int MaximumCabinetSizeForLargeFileSplitting;
83 int MaximumUncompressedMediaSize;
84 this.GetMediaTemplateAttributes(out MaximumCabinetSizeForLargeFileSplitting, out MaximumUncompressedMediaSize);
85 cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = MaximumCabinetSizeForLargeFileSplitting;
86 cabinetBuilder.MaximumUncompressedMediaSize = MaximumUncompressedMediaSize;
87
88 foreach (var entry in this.FileRowsByCabinet)
89 {
90 MediaRow mediaRow = entry.Key;
91 IEnumerable<FileFacade> files = entry.Value;
92 CompressionLevel compressionLevel = this.DefaultCompressionLevel;
93
94 WixMediaRow wixMediaRow = null;
95 string mediaLayoutFolder = null;
96
97 if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow))
98 {
99 mediaLayoutFolder = wixMediaRow.Layout;
100
101 if (wixMediaRow.CompressionLevel.HasValue)
102 {
103 compressionLevel = wixMediaRow.CompressionLevel.Value;
104 }
105 }
106
107 string cabinetDir = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory);
108
109 CabinetWorkItem cabinetWorkItem = this.CreateCabinetWorkItem(this.Output, cabinetDir, mediaRow, compressionLevel, files, this.fileTransfers);
110 if (null != cabinetWorkItem)
111 {
112 cabinetBuilder.Enqueue(cabinetWorkItem);
113 }
114 }
115
116 // stop processing if an error previously occurred
117 if (Messaging.Instance.EncounteredError)
118 {
119 return;
120 }
121
122 // create queued cabinets with multiple threads
123 cabinetBuilder.CreateQueuedCabinets();
124 if (Messaging.Instance.EncounteredError)
125 {
126 return;
127 }
128 }
129
130 /// <summary>
131 /// Sets the thead count to the number of processors if the current thread count is set to 0.
132 /// </summary>
133 /// <remarks>The thread count value must be greater than 0 otherwise and exception will be thrown.</remarks>
134 private void SetCabbingThreadCount()
135 {
136 // default the number of cabbing threads to the number of processors if it wasn't specified
137 if (0 == this.CabbingThreadCount)
138 {
139 string numberOfProcessors = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS");
140
141 try
142 {
143 if (null != numberOfProcessors)
144 {
145 this.CabbingThreadCount = Convert.ToInt32(numberOfProcessors, CultureInfo.InvariantCulture.NumberFormat);
146
147 if (0 >= this.CabbingThreadCount)
148 {
149 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
150 }
151 }
152 else // default to 1 if the environment variable is not set
153 {
154 this.CabbingThreadCount = 1;
155 }
156
157 Messaging.Instance.OnMessage(WixVerboses.SetCabbingThreadCount(this.CabbingThreadCount.ToString()));
158 }
159 catch (ArgumentException)
160 {
161 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
162 }
163 catch (FormatException)
164 {
165 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
166 }
167 }
168 }
169
170
171 /// <summary>
172 /// Creates a work item to create a cabinet.
173 /// </summary>
174 /// <param name="output">Output for the current database.</param>
175 /// <param name="cabinetDir">Directory to create cabinet in.</param>
176 /// <param name="mediaRow">MediaRow containing information about the cabinet.</param>
177 /// <param name="fileFacades">Collection of files in this cabinet.</param>
178 /// <param name="fileTransfers">Array of files to be transfered.</param>
179 /// <returns>created CabinetWorkItem object</returns>
180 private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaRow mediaRow, CompressionLevel compressionLevel, IEnumerable<FileFacade> fileFacades, List<FileTransfer> fileTransfers)
181 {
182 CabinetWorkItem cabinetWorkItem = null;
183 string tempCabinetFileX = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet);
184
185 // check for an empty cabinet
186 if (!fileFacades.Any())
187 {
188 string cabinetName = mediaRow.Cabinet;
189
190 // remove the leading '#' from the embedded cabinet name to make the warning easier to understand
191 if (cabinetName.StartsWith("#", StringComparison.Ordinal))
192 {
193 cabinetName = cabinetName.Substring(1);
194 }
195
196 // If building a patch, remind them to run -p for torch.
197 if (OutputType.Patch == output.Type)
198 {
199 Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName, true));
200 }
201 else
202 {
203 Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName));
204 }
205 }
206
207 ResolvedCabinet resolvedCabinet = this.ResolveCabinet(tempCabinetFileX, fileFacades);
208
209 // create a cabinet work item if it's not being skipped
210 if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption)
211 {
212 int maxThreshold = 0; // default to the threshold for best smartcabbing (makes smallest cabinet).
213
214 cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold, compressionLevel/*, this.FileManager*/);
215 }
216 else // reuse the cabinet from the cabinet cache.
217 {
218 Messaging.Instance.OnMessage(WixVerboses.ReusingCabCache(mediaRow.SourceLineNumbers, mediaRow.Cabinet, resolvedCabinet.Path));
219
220 try
221 {
222 // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The
223 // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that
224 // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from
225 // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output)
226 // causing the project to look like it perpetually needs a rebuild until all of the reused
227 // cabinets get newer timestamps.
228 File.SetLastWriteTime(resolvedCabinet.Path, DateTime.Now);
229 }
230 catch (Exception e)
231 {
232 Messaging.Instance.OnMessage(WixWarnings.CannotUpdateCabCache(mediaRow.SourceLineNumbers, resolvedCabinet.Path, e.Message));
233 }
234 }
235
236 if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal))
237 {
238 Table streamsTable = output.EnsureTable(this.TableDefinitions["_Streams"]);
239
240 Row streamRow = streamsTable.CreateRow(mediaRow.SourceLineNumbers);
241 streamRow[0] = mediaRow.Cabinet.Substring(1);
242 streamRow[1] = resolvedCabinet.Path;
243 }
244 else
245 {
246 string destinationPath = Path.Combine(cabinetDir, mediaRow.Cabinet);
247 FileTransfer transfer;
248 if (FileTransfer.TryCreate(resolvedCabinet.Path, destinationPath, CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption, "Cabinet", mediaRow.SourceLineNumbers, out transfer))
249 {
250 transfer.Built = true;
251 fileTransfers.Add(transfer);
252 }
253 }
254
255 return cabinetWorkItem;
256 }
257
258 private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades)
259 {
260 ResolvedCabinet resolved = null;
261
262 List<BindFileWithPath> filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList();
263
264 foreach (IBinderFileManager fileManager in this.FileManagers)
265 {
266 resolved = fileManager.ResolveCabinet(cabinetPath, filesWithPath);
267 if (null != resolved)
268 {
269 break;
270 }
271 }
272
273 return resolved;
274 }
275
276 /// <summary>
277 /// Delegate for Cabinet Split Callback
278 /// </summary>
279 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
280 internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken);
281
282 /// <summary>
283 /// Call back to Add File Transfer for new Cab and add new Cab to Media table
284 /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe
285 /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored
286 /// </summary>
287 /// <param name="firstCabName">The name of splitting cabinet without extention e.g. "cab1".</param>
288 /// <param name="newCabName">The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"</param>
289 /// <param name="fileToken">The file token of the first file present in the splitting cabinet</param>
290 internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken)
291 {
292 // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads
293 Mutex mutex = new Mutex(false, "WixCabinetSplitBinderCallback");
294 try
295 {
296 if (!mutex.WaitOne(0, false)) // Check if you can get the lock
297 {
298 // Cound not get the Lock
299 Messaging.Instance.OnMessage(WixVerboses.CabinetsSplitInParallel());
300 mutex.WaitOne(); // Wait on other thread
301 }
302
303 string firstCabinetName = firstCabName + ".cab";
304 string newCabinetName = newCabName;
305 bool transferAdded = false; // Used for Error Handling
306
307 // Create File Transfer for new Cabinet using transfer of Base Cabinet
308 foreach (FileTransfer transfer in this.FileTransfers)
309 {
310 if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase))
311 {
312 string newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName);
313 string newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName);
314
315 FileTransfer newTransfer;
316 if (FileTransfer.TryCreate(newCabSourcePath, newCabTargetPath, transfer.Move, "Cabinet", transfer.SourceLineNumbers, out newTransfer))
317 {
318 newTransfer.Built = true;
319 this.fileTransfers.Add(newTransfer);
320 transferAdded = true;
321 break;
322 }
323 }
324 }
325
326 // Check if File Transfer was added
327 if (!transferAdded)
328 {
329 throw new WixException(WixErrors.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName));
330 }
331
332 // Add the new Cabinets to media table using LastSequence of Base Cabinet
333 Table mediaTable = this.Output.Tables["Media"];
334 Table wixFileTable = this.Output.Tables["WixFile"];
335 int diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain
336 int lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain
337 bool lastSplitCabinetFound = false; // Used for Error Handling
338
339 string lastCabinetOfThisSequence = String.Empty;
340 // Get the Value of Last Cabinet Added in this split Sequence from Dictionary
341 if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence))
342 {
343 // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence
344 lastCabinetOfThisSequence = firstCabinetName;
345 }
346
347 foreach (MediaRow mediaRow in mediaTable.Rows)
348 {
349 // Get details for the Last Cabinet Added in this Split Sequence
350 if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
351 {
352 lastSequenceForLastSplitCabAdded = mediaRow.LastSequence;
353 diskIDForLastSplitCabAdded = mediaRow.DiskId;
354 lastSplitCabinetFound = true;
355 }
356
357 // Check for Name Collision for the new Cabinet added
358 if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
359 {
360 // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row
361 throw new WixException(WixErrors.SplitCabinetNameCollision(newCabinetName, firstCabinetName));
362 }
363 }
364
365 // Check if the last Split Cabinet was found in the Media Table
366 if (!lastSplitCabinetFound)
367 {
368 throw new WixException(WixErrors.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence));
369 }
370
371 // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort
372 // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with
373 // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction
374 MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null);
375 newMediaRow.Cabinet = newCabinetName;
376 newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion
377 newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded;
378
379 // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique
380 foreach (MediaRow mediaRow in mediaTable.Rows)
381 {
382 // Check if this row comes after inserted row and it is not the new cabinet inserted row
383 if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
384 {
385 mediaRow.DiskId++; // Increment DiskID
386 }
387 }
388
389 // Now Increment DiskID for All files Rows so that they refer to the right Media Row
390 foreach (WixFileRow wixFileRow in wixFileTable.Rows)
391 {
392 // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet
393 // This check will work as we have only one large file in every splitting cabinet
394 // If we want to support splitting cabinet with more large files we need to update this code
395 if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase))
396 {
397 wixFileRow.DiskId++; // Increment DiskID
398 }
399 }
400
401 // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback
402 this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName;
403
404 mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails
405 }
406 finally
407 {
408 // Releasing the Mutex here
409 mutex.ReleaseMutex();
410 }
411 }
412
413
414 /// <summary>
415 /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides
416 /// </summary>
417 /// <param name="output">Output to generate image for.</param>
418 /// <param name="fileRows">The indexed file rows.</param>
419 private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize)
420 {
421 // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size
422 string mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS");
423 string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
424 int maxCabSizeForLargeFileInMB = 0;
425 int maxPreCompressedSizeInMB = 0;
426 ulong testOverFlow = 0;
427
428 // Supply Compile MediaTemplate Attributes to Cabinet Builder
429 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
430 if (mediaTemplateTable != null)
431 {
432 WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0];
433
434 // Get the Value for Max Cab Size for File Splitting
435 try
436 {
437 // Override authored mcslfs value if environment variable is authored.
438 if (!String.IsNullOrEmpty(mcslfsString))
439 {
440 maxCabSizeForLargeFileInMB = Int32.Parse(mcslfsString);
441 }
442 else
443 {
444 maxCabSizeForLargeFileInMB = mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting;
445 }
446 testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024;
447 }
448 catch (FormatException)
449 {
450 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString));
451 }
452 catch (OverflowException)
453 {
454 throw new WixException(WixErrors.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, CompilerCore.MaxValueOfMaxCabSizeForLargeFileSplitting));
455 }
456
457 try
458 {
459 // Override authored mums value if environment variable is authored.
460 if (!String.IsNullOrEmpty(mumsString))
461 {
462 maxPreCompressedSizeInMB = Int32.Parse(mumsString);
463 }
464 else
465 {
466 maxPreCompressedSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize;
467 }
468 testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024;
469 }
470 catch (FormatException)
471 {
472 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString));
473 }
474 catch (OverflowException)
475 {
476 throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB));
477 }
478
479 maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB;
480 maxUncompressedMediaSize = maxPreCompressedSizeInMB;
481 }
482 else
483 {
484 maxCabSizeForLargeFileSplitting = 0;
485 maxUncompressedMediaSize = CompilerCore.DefaultMaximumUncompressedMediaSize;
486 }
487 }
488 }
489}