// 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. namespace WixToolset.Bind.Databases { using System; using System.Collections.Generic; using System.Globalization; using System.IO; using WixToolset.Data; using WixToolset.Data.Rows; /// /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows. /// public class AssignMediaCommand : ICommand { public AssignMediaCommand() { this.CabinetNameTemplate = "Cab{0}.cab"; } public Output Output { private get; set; } public bool FilesCompressed { private get; set; } public string CabinetNameTemplate { private get; set; } public IEnumerable FileFacades { private get; set; } public TableDefinitionCollection TableDefinitions { private get; set; } /// /// Gets cabinets with their file rows. /// public Dictionary> FileFacadesByCabinetMedia { get; private set; } /// /// Get media rows. /// public RowDictionary MediaRows { get; private set; } /// /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no. /// This contains all the files when Package element is marked with compression=no /// public IEnumerable UncompressedFileFacades { get; private set; } public void Execute() { Dictionary> filesByCabinetMedia = new Dictionary>(); RowDictionary mediaRows = new RowDictionary(); List uncompressedFiles = new List(); MediaRow mergeModuleMediaRow = null; Table mediaTable = this.Output.Tables["Media"]; Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"]; // If both tables are authored, it is an error. if ((mediaTemplateTable != null && mediaTemplateTable.Rows.Count > 0) && (mediaTable != null && mediaTable.Rows.Count > 1)) { throw new WixException(WixErrors.MediaTableCollision(null)); } // When building merge module, all the files go to "#MergeModule.CABinet". if (OutputType.Module == this.Output.Type) { Table mergeModuleMediaTable = new Table(null, this.TableDefinitions["Media"]); mergeModuleMediaRow = (MediaRow)mergeModuleMediaTable.CreateRow(null); mergeModuleMediaRow.Cabinet = "#MergeModule.CABinet"; filesByCabinetMedia.Add(mergeModuleMediaRow, new List()); } if (OutputType.Module == this.Output.Type || null == mediaTemplateTable) { this.ManuallyAssignFiles(mediaTable, mergeModuleMediaRow, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles); } else { this.AutoAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles); } this.FileFacadesByCabinetMedia = new Dictionary>(); foreach (var mediaRowWithFiles in filesByCabinetMedia) { this.FileFacadesByCabinetMedia.Add(mediaRowWithFiles.Key, mediaRowWithFiles.Value); } this.MediaRows = mediaRows; this.UncompressedFileFacades = uncompressedFiles; } /// /// Assign files to cabinets based on MediaTemplate authoring. /// /// FileRowCollection private void AutoAssignFiles(Table mediaTable, IEnumerable fileFacades, Dictionary> filesByCabinetMedia, RowDictionary mediaRows, List uncompressedFiles) { const int MaxCabIndex = 999; ulong currentPreCabSize = 0; ulong maxPreCabSizeInBytes; int maxPreCabSizeInMB = 0; int currentCabIndex = 0; MediaRow currentMediaRow = null; Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"]; // Auto assign files to cabinets based on maximum uncompressed media size mediaTable.Rows.Clear(); WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0]; if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate)) { this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate; } string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); try { // Override authored mums value if environment variable is authored. if (!String.IsNullOrEmpty(mumsString)) { maxPreCabSizeInMB = Int32.Parse(mumsString); } else { maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize; } maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024; } catch (FormatException) { throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); } catch (OverflowException) { throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB)); } foreach (FileFacade facade in this.FileFacades) { // When building a product, if the current file is not to be compressed or if // the package set not to be compressed, don't cab it. if (OutputType.Product == this.Output.Type && (YesNoType.No == facade.File.Compressed || (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed))) { uncompressedFiles.Add(facade); continue; } if (currentCabIndex == MaxCabIndex) { // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore. List cabinetFiles = filesByCabinetMedia[currentMediaRow]; facade.WixFile.DiskId = currentCabIndex; cabinetFiles.Add(facade); continue; } // Update current cab size. currentPreCabSize += (ulong)facade.File.FileSize; if (currentPreCabSize > maxPreCabSizeInBytes) { // Overflow due to current file currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex); mediaRows.Add(currentMediaRow); filesByCabinetMedia.Add(currentMediaRow, new List()); List cabinetFileRows = filesByCabinetMedia[currentMediaRow]; facade.WixFile.DiskId = currentCabIndex; cabinetFileRows.Add(facade); // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize currentPreCabSize = (ulong)facade.File.FileSize; } else { // File fits in the current cab. if (currentMediaRow == null) { // Create new cab and MediaRow currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex); mediaRows.Add(currentMediaRow); filesByCabinetMedia.Add(currentMediaRow, new List()); } // Associate current file with current cab. List cabinetFiles = filesByCabinetMedia[currentMediaRow]; facade.WixFile.DiskId = currentCabIndex; cabinetFiles.Add(facade); } } // If there are uncompressed files and no MediaRow, create a default one. if (uncompressedFiles.Count > 0 && mediaTable.Rows.Count == 0) { MediaRow defaultMediaRow = (MediaRow)mediaTable.CreateRow(null); defaultMediaRow.DiskId = 1; mediaRows.Add(defaultMediaRow); } } /// /// Assign files to cabinets based on Media authoring. /// /// /// /// private void ManuallyAssignFiles(Table mediaTable, MediaRow mergeModuleMediaRow, IEnumerable fileFacades, Dictionary> filesByCabinetMedia, RowDictionary mediaRows, List uncompressedFiles) { if (OutputType.Module != this.Output.Type) { if (null != mediaTable) { Dictionary cabinetMediaRows = new Dictionary(StringComparer.InvariantCultureIgnoreCase); foreach (MediaRow mediaRow in mediaTable.Rows) { // If the Media row has a cabinet, make sure it is unique across all Media rows. if (!String.IsNullOrEmpty(mediaRow.Cabinet)) { MediaRow existingRow; if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out existingRow)) { Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet)); Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet)); } else { cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow); } } mediaRows.Add(mediaRow); } } foreach (MediaRow mediaRow in mediaRows.Values) { if (null != mediaRow.Cabinet) { filesByCabinetMedia.Add(mediaRow, new List()); } } } foreach (FileFacade facade in fileFacades) { if (OutputType.Module == this.Output.Type) { filesByCabinetMedia[mergeModuleMediaRow].Add(facade); } else { MediaRow mediaRow; if (!mediaRows.TryGetValue(facade.WixFile.DiskId.ToString(CultureInfo.InvariantCulture), out mediaRow)) { Messaging.Instance.OnMessage(WixErrors.MissingMedia(facade.File.SourceLineNumbers, facade.WixFile.DiskId)); continue; } // When building a product, if the current file is not to be compressed or if // the package set not to be compressed, don't cab it. if (OutputType.Product == this.Output.Type && (YesNoType.No == facade.File.Compressed || (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed))) { uncompressedFiles.Add(facade); } else // file is marked compressed. { List cabinetFiles; if (filesByCabinetMedia.TryGetValue(mediaRow, out cabinetFiles)) { cabinetFiles.Add(facade); } else { Messaging.Instance.OnMessage(WixErrors.ExpectedMediaCabinet(facade.File.SourceLineNumbers, facade.File.File, facade.WixFile.DiskId)); } } } } } /// /// Adds a row to the media table with cab name template filled in. /// /// /// /// private MediaRow AddMediaRow(WixMediaTemplateRow mediaTemplateRow, Table mediaTable, int cabIndex) { MediaRow currentMediaRow = (MediaRow)mediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers); currentMediaRow.DiskId = cabIndex; currentMediaRow.Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex); Table wixMediaTable = this.Output.EnsureTable(this.TableDefinitions["WixMedia"]); WixMediaRow row = (WixMediaRow)wixMediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers); row.DiskId = cabIndex; row.CompressionLevel = mediaTemplateRow.CompressionLevel; return currentMediaRow; } } }