From 16ed873f1bd7f69a61fd2effdab9e57203b896e6 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Mon, 1 Aug 2022 19:35:50 -0700 Subject: Implement cabinet spanning Completes 6368 --- src/api/wix/WixToolset.Data/WarningMessages.cs | 12 +- src/libs/dutil/WixToolset.DUtil/inc/cabcutil.h | 2 +- src/wix/WixToolset.Core.Native/Cabinet.cs | 26 +- src/wix/WixToolset.Core.Native/CabinetCreated.cs | 31 ++ src/wix/WixToolset.Core.Native/WixNativeExe.cs | 4 +- .../Bind/BindDatabaseCommand.cs | 30 +- .../Bind/CabinetBuilder.cs | 65 ++-- .../Bind/CabinetWorkItem.cs | 11 +- .../Bind/CompletedCabinetWorkItem.cs | 20 ++ .../Bind/CreateCabinetsCommand.cs | 400 +++++++++------------ .../Bind/ProcessUncompressedFilesCommand.cs | 26 +- .../ExtensibilityServices/FileFacade.cs | 5 +- .../WixToolsetTest.Core.Native/CabinetFixture.cs | 42 ++- .../MsiCabinetFixture.cs | 256 +++++++++++++ .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 125 +------ .../MsiCabinet/MultiFileSpanningCabinets.wxs | 38 ++ .../TestData/MsiCabinet/data/ced.txt | 1 + .../TestData/MsiCabinet/data/hij.txt | 1 + .../TestData/MsiCabinet/data/qrs.txt | 1 + .../TestData/MsiCabinet/data/tuv.txt | 1 + src/wix/wixnative/smartcab.cpp | 30 +- 21 files changed, 688 insertions(+), 439 deletions(-) create mode 100644 src/wix/WixToolset.Core.Native/CabinetCreated.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CompletedCabinetWorkItem.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/MsiCabinetFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/MultiFileSpanningCabinets.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/ced.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/hij.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/qrs.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/tuv.txt (limited to 'src') diff --git a/src/api/wix/WixToolset.Data/WarningMessages.cs b/src/api/wix/WixToolset.Data/WarningMessages.cs index d39ead3d..6e2775bb 100644 --- a/src/api/wix/WixToolset.Data/WarningMessages.cs +++ b/src/api/wix/WixToolset.Data/WarningMessages.cs @@ -267,14 +267,14 @@ namespace WixToolset.Data return Message(sourceLineNumbers, Ids.EmptyAttributeValue, "The {0}/@{1} attribute's value cannot be an empty string. If you want the value to be null or empty, simply remove the entire attribute.", elementName, attributeName); } - public static Message EmptyCabinet(SourceLineNumber sourceLineNumbers, string cabinetName) - { - return Message(sourceLineNumbers, Ids.EmptyCabinet, "The cabinet '{0}' does not contain any files. If this installation contains no files, this warning can likely be safely ignored. Otherwise, please add files to the cabinet or remove it.", cabinetName); - } - public static Message EmptyCabinet(SourceLineNumber sourceLineNumbers, string cabinetName, bool isPatch) { - return Message(sourceLineNumbers, Ids.EmptyCabinet, "The cabinet '{0}' does not contain any files. If this patch contains no files, this warning can likely be safely ignored. Otherwise, try passing -p to torch.exe when first building the transforms, or add a ComponentRef to your PatchFamily authoring to pull changed files into the cabinet.", cabinetName, isPatch); + if (isPatch) + { + return Message(sourceLineNumbers, Ids.EmptyCabinet, "The cabinet '{0}' does not contain any files. If this patch contains no files, this warning can likely be safely ignored. Otherwise, try passing -p to torch.exe when first building the transforms, or add a ComponentRef to your PatchFamily authoring to pull changed files into the cabinet.", cabinetName, isPatch); + } + + return Message(sourceLineNumbers, Ids.EmptyCabinet, "The cabinet '{0}' does not contain any files. If this installation contains no files, this warning can likely be safely ignored. Otherwise, please add files to the cabinet or remove it.", cabinetName); } public static Message ExpectedForeignRow(SourceLineNumber sourceLineNumbers, string tableName, string primaryKey, string columnName, string columnValue, string foreignTableName) diff --git a/src/libs/dutil/WixToolset.DUtil/inc/cabcutil.h b/src/libs/dutil/WixToolset.DUtil/inc/cabcutil.h index 4f0c7b13..09784b7d 100644 --- a/src/libs/dutil/WixToolset.DUtil/inc/cabcutil.h +++ b/src/libs/dutil/WixToolset.DUtil/inc/cabcutil.h @@ -10,7 +10,7 @@ // First argument is the name of splitting cabinet without extension e.g. "cab1" // Second argument is name of the new cabinet that would be formed by splitting e.g. "cab1b.cab" // Third argument is the file token of the first file present in the splitting cabinet -typedef void (__stdcall * FileSplitCabNamesCallback)(LPWSTR, LPWSTR, LPWSTR); +typedef void (__stdcall * FileSplitCabNamesCallback)(LPCWSTR, LPCWSTR, LPCWSTR); #define CAB_MAX_SIZE 0x7FFFFFFF // (see KB: Q174866) diff --git a/src/wix/WixToolset.Core.Native/Cabinet.cs b/src/wix/WixToolset.Core.Native/Cabinet.cs index e383d8fa..4d8ddb44 100644 --- a/src/wix/WixToolset.Core.Native/Cabinet.cs +++ b/src/wix/WixToolset.Core.Native/Cabinet.cs @@ -36,7 +36,8 @@ namespace WixToolset.Core.Native /// Level of compression to apply. /// Maximum size of cabinet. /// Maximum threshold for each cabinet. - public void Compress(IEnumerable files, CompressionLevel compressionLevel, int maxSize = 0, int maxThresh = 0) + /// >List of CabinetCreated. + public IReadOnlyCollection Compress(IEnumerable files, CompressionLevel compressionLevel, int maxSize = 0, int maxThresh = 0) { var compressionLevelVariable = Environment.GetEnvironmentVariable(CompressionLevelVariable); @@ -56,7 +57,9 @@ namespace WixToolset.Core.Native wixnative.AddStdinLine(file.ToWixNativeStdinLine()); } - wixnative.Run(); + var cabinetsCreated = wixnative.Run(); + + return ParseCreatedCabinets(cabinetsCreated); } /// @@ -103,5 +106,24 @@ namespace WixToolset.Core.Native var wixnative = new WixNativeExe("extractcab", this.Path, outputFolder); return wixnative.Run().Where(output => !String.IsNullOrWhiteSpace(output)); } + + private static IReadOnlyCollection ParseCreatedCabinets(IReadOnlyCollection cabinetsCreated) + { + var created = new List(); + + foreach (var cabinetCreated in cabinetsCreated) + { + var data = cabinetCreated.Split(TextLineSplitter, StringSplitOptions.None); + + if (data.Length != 3) + { + continue; + } + + created.Add(new CabinetCreated(data[1], data[2])); + } + + return created; + } } } diff --git a/src/wix/WixToolset.Core.Native/CabinetCreated.cs b/src/wix/WixToolset.Core.Native/CabinetCreated.cs new file mode 100644 index 00000000..635c862f --- /dev/null +++ b/src/wix/WixToolset.Core.Native/CabinetCreated.cs @@ -0,0 +1,31 @@ +// 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.Core.Native +{ + /// + /// Cabinet created by compressing files. + /// + public sealed class CabinetCreated + { + /// + /// Constructs CabinetCreated. + /// + /// Name of cabinet. + /// Token of first file compressed in cabinet. + public CabinetCreated(string cabinetName, string firstFileToken) + { + this.CabinetName = cabinetName; + this.FirstFileToken = firstFileToken; + } + + /// + /// Gets the name of the cabinet. + /// + public string CabinetName { get; } + + /// + /// Gets the token of the first file in the cabinet. + /// + public string FirstFileToken { get; } + } +} diff --git a/src/wix/WixToolset.Core.Native/WixNativeExe.cs b/src/wix/WixToolset.Core.Native/WixNativeExe.cs index 90439c5d..b3b248c0 100644 --- a/src/wix/WixToolset.Core.Native/WixNativeExe.cs +++ b/src/wix/WixToolset.Core.Native/WixNativeExe.cs @@ -49,8 +49,8 @@ namespace WixToolset.Core.Native using (var process = Process.Start(wixNativeInfo)) { - process.OutputDataReceived += (s, a) => outputLines.Add(a.Data); - process.ErrorDataReceived += (s, a) => outputLines.Add(a.Data); + process.OutputDataReceived += (s, a) => { if (a.Data != null) { outputLines.Add(a.Data); } }; + process.ErrorDataReceived += (s, a) => { if (a.Data != null) { outputLines.Add(a.Data); } }; process.BeginOutputReadLine(); process.BeginErrorReadLine(); diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 0e26bf6f..1589cc7a 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -410,7 +410,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var modularize = new ModularizeCommand(this.WindowsInstallerBackendHelper, data, modularizationSuffix, section.Symbols.OfType()); modularize.Execute(); - // Ensure all sequence tables in place because, mergemod.dll requires them. + // Ensure all sequence tables in place because mergemod.dll requires them. var unsuppress = new AddBackSuppressedSequenceTablesCommand(data, tableDefinitions); suppressedTableNames = unsuppress.Execute(); } @@ -438,28 +438,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind command.Execute(); } - // create cabinet files and process uncompressed files - var layoutDirectory = Path.GetDirectoryName(this.OutputPath); + // Create cabinet files. if (!this.SuppressLayout || OutputType.Module == data.Type) { this.Messaging.Write(VerboseMessages.CreatingCabinetFiles()); - var mediaTemplate = section.Symbols.OfType().FirstOrDefault(); - - var command = new CreateCabinetsCommand(this.ServiceProvider, this.WindowsInstallerBackendHelper, mediaTemplate); - command.CabbingThreadCount = this.CabbingThreadCount; - command.CabCachePath = this.CabCachePath; - command.DefaultCompressionLevel = this.DefaultCompressionLevel; - command.Data = data; - command.Messaging = this.Messaging; - command.BackendExtensions = this.BackendExtensions; - command.LayoutDirectory = layoutDirectory; - command.Compressed = compressed; - command.ModularizationSuffix = modularizationSuffix; - command.FileFacadesByCabinet = filesByCabinetMedia; - command.ResolveMedia = this.ResolveMedia; - command.TableDefinitions = tableDefinitions; - command.IntermediateFolder = this.IntermediateFolder; + var command = new CreateCabinetsCommand(this.ServiceProvider, this.Messaging, this.WindowsInstallerBackendHelper, this.BackendExtensions, section, this.CabCachePath, this.CabbingThreadCount, this.OutputPath, this.IntermediateFolder, this.DefaultCompressionLevel, compressed, modularizationSuffix, filesByCabinetMedia, data, tableDefinitions, this.ResolveMedia); command.Execute(); fileTransfers.AddRange(command.FileTransfers); @@ -523,13 +507,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Process uncompressed files. if (!this.SuppressLayout && uncompressedFiles.Any()) { - var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver); - command.Compressed = compressed; - command.FileFacades = uncompressedFiles; - command.LayoutDirectory = layoutDirectory; - command.LongNamesInImage = longNames; - command.ResolveMedia = this.ResolveMedia; - command.DatabasePath = this.OutputPath; + var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver, uncompressedFiles, this.OutputPath, compressed, longNames, this.ResolveMedia); command.Execute(); fileTransfers.AddRange(command.FileTransfers); diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs index 49eaad42..9acbe475 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs @@ -18,18 +18,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind internal sealed class CabinetBuilder { private readonly Queue cabinetWorkItems; - private int threadCount; + private readonly List completedCabinets; - // Address of Binder's callback function for Cabinet Splitting - private readonly IntPtr newCabNamesCallBackAddress; - - /// - /// Instantiate a new CabinetBuilder. - /// - /// - /// number of threads to use - /// Address of Binder's callback function for Cabinet Splitting - public CabinetBuilder(IMessaging messaging, int threadCount, IntPtr newCabNamesCallBackAddress) + public CabinetBuilder(IMessaging messaging, int threadCount, int maximumCabinetSizeForLargeFileSplitting, int maximumUncompressedMediaSize) { if (0 >= threadCount) { @@ -37,24 +28,32 @@ namespace WixToolset.Core.WindowsInstaller.Bind } this.cabinetWorkItems = new Queue(); - this.Messaging = messaging; - this.threadCount = threadCount; + this.completedCabinets = new List(); - // Set Address of Binder's callback function for Cabinet Splitting - this.newCabNamesCallBackAddress = newCabNamesCallBackAddress; + this.Messaging = messaging; + this.ThreadCount = threadCount; + this.MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting; + this.MaximumUncompressedMediaSize = maximumUncompressedMediaSize; } private IMessaging Messaging { get; } - public int MaximumCabinetSizeForLargeFileSplitting { get; set; } + private int ThreadCount { get; } - public int MaximumUncompressedMediaSize { get; set; } + private int MaximumCabinetSizeForLargeFileSplitting { get; } + + private int MaximumUncompressedMediaSize { get; } + + public IReadOnlyCollection CompletedCabinets => this.completedCabinets; /// /// Enqueues a CabinetWorkItem to the queue. /// /// cabinet work item - public void Enqueue(CabinetWorkItem cabinetWorkItem) => this.cabinetWorkItems.Enqueue(cabinetWorkItem); + public void Enqueue(CabinetWorkItem cabinetWorkItem) + { + this.cabinetWorkItems.Enqueue(cabinetWorkItem); + } /// /// Create the queued cabinets. @@ -62,8 +61,20 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// error message number (zero if no error) public void CreateQueuedCabinets() { + if (this.cabinetWorkItems.Count == 0) + { + return; + } + + var cabinetFolders = this.cabinetWorkItems.Select(c => Path.GetDirectoryName(c.CabinetFile)).Distinct(StringComparer.OrdinalIgnoreCase); + + foreach (var folder in cabinetFolders) + { + Directory.CreateDirectory(folder); + } + // don't create more threads than the number of cabinets to build - var numberOfThreads = Math.Min(this.threadCount, this.cabinetWorkItems.Count); + var numberOfThreads = Math.Min(this.ThreadCount, this.cabinetWorkItems.Count); if (0 < numberOfThreads) { @@ -107,8 +118,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind cabinetWorkItem = this.cabinetWorkItems.Dequeue(); } - // create a cabinet - this.CreateCabinet(cabinetWorkItem); + // Create a cabinet. + var created = this.CreateCabinet(cabinetWorkItem); + + // Update the cabinet work item to report back what cabinets were created. + lock (this.completedCabinets) + { + this.completedCabinets.Add(new CompletedCabinetWorkItem(cabinetWorkItem.DiskId, created)); + } } } catch (WixException we) @@ -125,7 +142,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// Creates a cabinet using the wixcab.dll interop layer. /// /// CabinetWorkItem containing information about the cabinet to create. - private void CreateCabinet(CabinetWorkItem cabinetWorkItem) + private IReadOnlyCollection CreateCabinet(CabinetWorkItem cabinetWorkItem) { this.Messaging.Write(VerboseMessages.CreateCabinet(cabinetWorkItem.CabinetFile)); @@ -163,7 +180,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind .ToList(); var cab = new Cabinet(cabinetPath); - cab.Compress(files, cabinetWorkItem.CompressionLevel, maxCabinetSize, cabinetWorkItem.MaxThreshold); + var created = cab.Compress(files, cabinetWorkItem.CompressionLevel, maxCabinetSize, cabinetWorkItem.MaxThreshold); // Best effort check to see if the cabinet is too large for the Windows Installer. try @@ -177,6 +194,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind catch { } + + return created; } } } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs index b1a29834..12332c80 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs @@ -15,14 +15,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// Instantiate a new CabinetWorkItem. /// /// Source line number that requires the cabinet creation. + /// /// The collection of files in this cabinet. /// The cabinet file. /// Maximum threshold for each cabinet. /// The compression level of the cabinet. /// Modularization suffix used when building a Merge Module. - public CabinetWorkItem(SourceLineNumber sourceLineNumber, string cabinetFile, IEnumerable fileFacades, int maxThreshold, CompressionLevel compressionLevel, string modularizationSuffix) + public CabinetWorkItem(SourceLineNumber sourceLineNumber, int diskId, string cabinetFile, IEnumerable fileFacades, int maxThreshold, CompressionLevel compressionLevel, string modularizationSuffix) { this.SourceLineNumber = sourceLineNumber; + this.DiskId = diskId; this.CabinetFile = cabinetFile; this.CompressionLevel = compressionLevel; this.ModularizationSuffix = modularizationSuffix; @@ -35,6 +37,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// public SourceLineNumber SourceLineNumber { get; } + /// + /// Gets the Media symbol's DiskId that requires the cabinet. + /// + public int DiskId { get; } + /// /// Gets the cabinet file. /// @@ -56,7 +63,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// Gets the collection of files in this cabinet. /// /// The collection of files in this cabinet. - public IEnumerable FileFacades { get; } + public IEnumerable FileFacades { get; } /// /// Gets the max threshold. diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CompletedCabinetWorkItem.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CompletedCabinetWorkItem.cs new file mode 100644 index 00000000..e916bb61 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CompletedCabinetWorkItem.cs @@ -0,0 +1,20 @@ +// 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.Core.WindowsInstaller.Bind +{ + using System.Collections.Generic; + using WixToolset.Core.Native; + + internal class CompletedCabinetWorkItem + { + public CompletedCabinetWorkItem(int diskId, IReadOnlyCollection created) + { + this.DiskId = diskId; + this.CreatedCabinets = created; + } + + public int DiskId { get; } + + public IReadOnlyCollection CreatedCabinets { get; } + } +} 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 { using System; using System.Collections.Generic; - using System.Globalization; using System.IO; using System.Linq; - using System.Runtime.InteropServices; using WixToolset.Data; using WixToolset.Data.Symbols; using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; using WixToolset.Extensibility; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; @@ -20,70 +19,63 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// internal class CreateCabinetsCommand { - public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB - public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) + public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB + public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) + private readonly CabinetResolver cabinetResolver; private readonly List fileTransfers; - private readonly List trackedFiles; - private readonly FileSplitCabNamesCallback newCabNamesCallBack; - - private Dictionary lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence - - public CreateCabinetsCommand(IServiceProvider serviceProvider, IBackendHelper backendHelper, WixMediaTemplateSymbol mediaTemplate) + public CreateCabinetsCommand(IServiceProvider serviceProvider, IMessaging messaging, IBackendHelper backendHelper, IEnumerable backendExtensions, IntermediateSection section, string cabCachePath, int cabbingThreadCount, string outputPath, string intermediateFolder, CompressionLevel? defaultCompressionLevel, bool compressed, string modularizationSuffix, Dictionary> filesByCabinetMedia, WindowsInstallerData data, TableDefinitionCollection tableDefinitions, Func resolveMedia) { - this.fileTransfers = new List(); + this.Messaging = messaging; - this.trackedFiles = new List(); - - this.newCabNamesCallBack = this.NewCabNamesCallBack; + this.BackendHelper = backendHelper; - this.ServiceProvider = serviceProvider; + this.Section = section; - this.BackendHelper = backendHelper; + this.CabbingThreadCount = cabbingThreadCount; - this.MediaTemplate = mediaTemplate; - } + this.IntermediateFolder = intermediateFolder; + this.LayoutDirectory = Path.GetDirectoryName(outputPath); - private IServiceProvider ServiceProvider { get; } + this.DefaultCompressionLevel = defaultCompressionLevel; + this.ModularizationSuffix = modularizationSuffix; + this.FileFacadesByCabinet = filesByCabinetMedia; - private IBackendHelper BackendHelper { get; } + this.Data = data; + this.TableDefinitions = tableDefinitions; - private WixMediaTemplateSymbol MediaTemplate { get; } + this.ResolveMedia = resolveMedia; - /// - /// Sets the number of threads to use for cabinet creation. - /// - public int CabbingThreadCount { private get; set; } + this.cabinetResolver = new CabinetResolver(serviceProvider, cabCachePath, backendExtensions); + this.fileTransfers = new List(); + this.trackedFiles = new List(); + } - public string CabCachePath { private get; set; } + private IMessaging Messaging { get; } - public IMessaging Messaging { private get; set; } + private IBackendHelper BackendHelper { get; } - public string IntermediateFolder { private get; set; } + private IntermediateSection Section { get; } - /// - /// Sets the default compression level to use for cabinets - /// that don't have their compression level explicitly set. - /// - public CompressionLevel? DefaultCompressionLevel { private get; set; } + private int CabbingThreadCount { get; set; } - public IEnumerable BackendExtensions { private get; set; } + private string IntermediateFolder { get; } - public WindowsInstallerData Data { private get; set; } + private string LayoutDirectory { get; } - public string LayoutDirectory { private get; set; } + private CompressionLevel? DefaultCompressionLevel { get; } - public bool Compressed { private get; set; } + private string ModularizationSuffix { get; } - public string ModularizationSuffix { private get; set; } + private Dictionary> FileFacadesByCabinet { get; } - public Dictionary> FileFacadesByCabinet { private get; set; } + private WindowsInstallerData Data { get; } - public Func ResolveMedia { private get; set; } + private TableDefinitionCollection TableDefinitions { get; } - public TableDefinitionCollection TableDefinitions { private get; set; } + private Func ResolveMedia { get; } public IEnumerable FileTransfers => this.fileTransfers; @@ -91,8 +83,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind public void Execute() { - this.lastCabinetAddedToMediaTable = new Dictionary(); - // If the cabbing thread count wasn't provided, default the number of cabbing threads to the number of processors. if (this.CabbingThreadCount <= 0) { @@ -101,13 +91,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.Messaging.Write(VerboseMessages.SetCabbingThreadCount(this.CabbingThreadCount.ToString())); } - // Send Binder object to Facilitate NewCabNamesCallBack Callback - var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack)); - - // Supply Compile MediaTemplate Attributes to Cabinet Builder this.GetMediaTemplateAttributes(out var maximumCabinetSizeForLargeFileSplitting, out var maximumUncompressedMediaSize); - cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting; - cabinetBuilder.MaximumUncompressedMediaSize = maximumUncompressedMediaSize; + + var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, maximumCabinetSizeForLargeFileSplitting, maximumUncompressedMediaSize); foreach (var entry in this.FileFacadesByCabinet) { @@ -129,12 +115,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind return; } - // create queued cabinets with multiple threads + // Create queued cabinets with multiple threads. cabinetBuilder.CreateQueuedCabinets(); + if (this.Messaging.EncounteredError) { return; } + + this.UpdateMediaWithSpannedCabinets(cabinetBuilder.CompletedCabinets); } private int CalculateCabbingThreadCount() @@ -163,7 +152,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind private CabinetWorkItem CreateCabinetWorkItem(WindowsInstallerData data, string cabinetDir, MediaSymbol mediaSymbol, CompressionLevel compressionLevel, IEnumerable fileFacades) { CabinetWorkItem cabinetWorkItem = null; - var tempCabinetFileX = Path.Combine(this.IntermediateFolder, mediaSymbol.Cabinet); + + var intermediateCabinetPath = Path.Combine(this.IntermediateFolder, mediaSymbol.Cabinet); // check for an empty cabinet if (!fileFacades.Any()) @@ -172,25 +162,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind var cabinetName = mediaSymbol.Cabinet.TrimStart('#'); // If building a patch, remind them to run -p for torch. - if (OutputType.Patch == data.Type) - { - this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName, true)); - } - else - { - this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName)); - } + this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName, OutputType.Patch == data.Type)); } - var cabinetResolver = new CabinetResolver(this.ServiceProvider, this.CabCachePath, this.BackendExtensions); - - var resolvedCabinet = cabinetResolver.ResolveCabinet(tempCabinetFileX, fileFacades); + var resolvedCabinet = this.cabinetResolver.ResolveCabinet(intermediateCabinetPath, fileFacades); - // create a cabinet work item if it's not being skipped + // Create a cabinet work item if it's not being skipped. if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption) { // Default to the threshold for best smartcabbing (makes smallest cabinet). - cabinetWorkItem = new CabinetWorkItem(mediaSymbol.SourceLineNumbers, resolvedCabinet.Path, fileFacades, maxThreshold: 0, compressionLevel: compressionLevel, modularizationSuffix: this.ModularizationSuffix); + cabinetWorkItem = new CabinetWorkItem(mediaSymbol.SourceLineNumbers, mediaSymbol.DiskId, resolvedCabinet.Path, fileFacades, maxThreshold: 0, compressionLevel: compressionLevel, modularizationSuffix: this.ModularizationSuffix); } else // reuse the cabinet from the cabinet cache. { @@ -235,185 +216,26 @@ namespace WixToolset.Core.WindowsInstaller.Bind return cabinetWorkItem; } - //private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable fileFacades) - //{ - // ResolvedCabinet resolved = null; - - // List filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList(); - - // foreach (var extension in this.BackendExtensions) - // { - // resolved = extension.ResolveCabinet(cabinetPath, filesWithPath); - // if (null != resolved) - // { - // break; - // } - // } - - // return resolved; - //} - - /// - /// Delegate for Cabinet Split Callback - /// - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken); - - /// - /// Call back to Add File Transfer for new Cab and add new Cab to Media table - /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe - /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored - /// - /// The name of splitting cabinet without extention e.g. "cab1". - /// The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab" - /// The file token of the first file present in the splitting cabinet - internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabinetName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken) - { - throw new NotImplementedException(); -#if TODO_CAB_SPANNING - // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads - var mutex = new Mutex(false, "WixCabinetSplitBinderCallback"); - try - { - if (!mutex.WaitOne(0, false)) // Check if you can get the lock - { - // Cound not get the Lock - this.Messaging.Write(VerboseMessages.CabinetsSplitInParallel()); - mutex.WaitOne(); // Wait on other thread - } - - var firstCabinetName = firstCabName + ".cab"; - var transferAdded = false; // Used for Error Handling - - // Create File Transfer for new Cabinet using transfer of Base Cabinet - foreach (var transfer in this.FileTransfers) - { - if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase)) - { - var newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName); - var newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName); - - var trackSource = this.BackendHelper.TrackFile(newCabSourcePath, TrackedFileType.Intermediate, transfer.SourceLineNumbers); - this.trackedFiles.Add(trackSource); - - var trackTarget = this.BackendHelper.TrackFile(newCabTargetPath, TrackedFileType.Final, transfer.SourceLineNumbers); - this.trackedFiles.Add(trackTarget); - - var newTransfer = this.BackendHelper.CreateFileTransfer(trackSource.Path, trackTarget.Path, transfer.Move, transfer.SourceLineNumbers); - this.fileTransfers.Add(newTransfer); - - transferAdded = true; - break; - } - } - - // Check if File Transfer was added - if (!transferAdded) - { - throw new WixException(ErrorMessages.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName)); - } - - // Add the new Cabinets to media table using LastSequence of Base Cabinet - var mediaTable = this.Output.Tables["Media"]; - var wixFileTable = this.Output.Tables["WixFile"]; - var diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain - var lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain - var lastSplitCabinetFound = false; // Used for Error Handling - - var lastCabinetOfThisSequence = String.Empty; - // Get the Value of Last Cabinet Added in this split Sequence from Dictionary - if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence)) - { - // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence - lastCabinetOfThisSequence = firstCabinetName; - } - - foreach (MediaRow mediaRow in mediaTable.Rows) - { - // Get details for the Last Cabinet Added in this Split Sequence - if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) - { - lastSequenceForLastSplitCabAdded = mediaRow.LastSequence; - diskIDForLastSplitCabAdded = mediaRow.DiskId; - lastSplitCabinetFound = true; - } - - // Check for Name Collision for the new Cabinet added - if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) - { - // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row - throw new WixException(ErrorMessages.SplitCabinetNameCollision(newCabinetName, firstCabinetName)); - } - } - - // Check if the last Split Cabinet was found in the Media Table - if (!lastSplitCabinetFound) - { - throw new WixException(ErrorMessages.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence)); - } - - // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort - // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with - // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction - MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null); - newMediaRow.Cabinet = newCabinetName; - newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion - newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded; - - // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique - foreach (MediaRow mediaRow in mediaTable.Rows) - { - // Check if this row comes after inserted row and it is not the new cabinet inserted row - if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) - { - mediaRow.DiskId++; // Increment DiskID - } - } - - // Now Increment DiskID for All files Rows so that they refer to the right Media Row - foreach (WixFileRow wixFileRow in wixFileTable.Rows) - { - // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet - // This check will work as we have only one large file in every splitting cabinet - // If we want to support splitting cabinet with more large files we need to update this code - if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase)) - { - wixFileRow.DiskId++; // Increment DiskID - } - } - - // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback - this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName; - - mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails - } - finally - { - // Releasing the Mutex here - mutex.ReleaseMutex(); - } -#endif - } - - /// /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides /// private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize) { - // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size - var mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS"); - var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); + var mediaTemplate = this.Section.Symbols.OfType().FirstOrDefault(); // Supply Compile MediaTemplate Attributes to Cabinet Builder - if (this.MediaTemplate != null) + if (mediaTemplate != null) { + // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size + var mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS"); + var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); + // Get the Value for Max Cab Size for File Splitting var maxCabSizeForLargeFileInMB = 0; try { // Override authored mcslfs value if environment variable is authored. - maxCabSizeForLargeFileInMB = !String.IsNullOrEmpty(mcslfsString) ? Int32.Parse(mcslfsString) : this.MediaTemplate.MaximumCabinetSizeForLargeFileSplitting ?? MaxValueOfMaxCabSizeForLargeFileSplitting; + maxCabSizeForLargeFileInMB = !String.IsNullOrEmpty(mcslfsString) ? Int32.Parse(mcslfsString) : mediaTemplate.MaximumCabinetSizeForLargeFileSplitting ?? MaxValueOfMaxCabSizeForLargeFileSplitting; var testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024; maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB; @@ -431,7 +253,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind try { // Override authored mums value if environment variable is authored. - maxPreCompressedSizeInMB = !String.IsNullOrEmpty(mumsString) ? Int32.Parse(mumsString) : this.MediaTemplate.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize; + maxPreCompressedSizeInMB = !String.IsNullOrEmpty(mumsString) ? Int32.Parse(mumsString) : mediaTemplate.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize; var testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024; maxUncompressedMediaSize = maxPreCompressedSizeInMB; @@ -451,5 +273,121 @@ namespace WixToolset.Core.WindowsInstaller.Bind maxUncompressedMediaSize = DefaultMaximumUncompressedMediaSize; } } + + private void UpdateMediaWithSpannedCabinets(IReadOnlyCollection completedCabinetWorkItems) + { + var completedCabinetsSpanned = completedCabinetWorkItems.Where(c => c.CreatedCabinets.Count > 1).OrderBy(c => c.DiskId).ToList(); + + if (completedCabinetsSpanned.Count == 0) + { + return; + } + + var fileTransfersByName = this.fileTransfers.ToDictionary(t => Path.GetFileName(t.Source), StringComparer.OrdinalIgnoreCase); + var mediaTable = this.Data.Tables["Media"]; + var fileTable = this.Data.Tables["File"]; + var mediaRows = mediaTable.Rows.Cast().OrderBy(m => m.DiskId).ToList(); + var fileRows = fileTable.Rows.Cast().OrderBy(f => f.Sequence).ToList(); + + var mediaRowsByOriginalDiskId = mediaRows.ToDictionary(m => m.DiskId); + var addedMediaRows = new List(); + + foreach (var completedCabinetSpanned in completedCabinetsSpanned) + { + var cabinet = completedCabinetSpanned.CreatedCabinets.First(); + var spannedCabinets = completedCabinetSpanned.CreatedCabinets.Skip(1); + + if (!fileTransfersByName.TryGetValue(cabinet.CabinetName, out var transfer) || + !mediaRowsByOriginalDiskId.TryGetValue(completedCabinetSpanned.DiskId, out var mediaRow)) + { + throw new WixException(ErrorMessages.SplitCabinetCopyRegistrationFailed(spannedCabinets.First().CabinetName, cabinet.CabinetName)); + } + + var lastDiskId = mediaRow.DiskId; + var mediaRowsThatWillNeedDiskIdUpdated = mediaRows.OrderBy(m => m.DiskId).Where(m => m.DiskId > mediaRow.DiskId).ToList(); + + foreach (var spannedCabinet in spannedCabinets) + { + var spannedCabinetSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), spannedCabinet.CabinetName); + var spannedCabinetTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), spannedCabinet.CabinetName); + + var trackSource = this.BackendHelper.TrackFile(spannedCabinetSourcePath, TrackedFileType.Intermediate, transfer.SourceLineNumbers); + this.trackedFiles.Add(trackSource); + + var trackTarget = this.BackendHelper.TrackFile(spannedCabinetTargetPath, TrackedFileType.BuiltOutput, transfer.SourceLineNumbers); + this.trackedFiles.Add(trackTarget); + + var newTransfer = this.BackendHelper.CreateFileTransfer(trackSource.Path, trackTarget.Path, transfer.Move, transfer.SourceLineNumbers); + this.fileTransfers.Add(newTransfer); + + // FDI Extract requires DiskID of Split Cabinets to be continuous. So a new Media row must inserted just + // after the previous spanned cabinet according to DiskID sort order, otherwise Windows Installer will + // encounter Error 2350 (FDI Server Error). + var newMediaRow = (MediaRow)mediaTable.CreateRow(mediaRow.SourceLineNumbers); + newMediaRow.Cabinet = spannedCabinet.CabinetName; + newMediaRow.DiskId = ++lastDiskId; + newMediaRow.LastSequence = mediaRow.LastSequence; + + addedMediaRows.Add(newMediaRow); + } + + // Increment the DiskId for all Media rows that come after the newly inserted row to ensure that the DiskId is unique + // and the Media rows stay in order based on last sequence. + foreach (var updateMediaRow in mediaRowsThatWillNeedDiskIdUpdated) + { + updateMediaRow.DiskId = ++lastDiskId; + } + } + + mediaTable.ValidateRows(); + + var oldDiskIdToNewDiskId = mediaRowsByOriginalDiskId.Where(originalDiskIdWithMediaRow => originalDiskIdWithMediaRow.Value.DiskId != originalDiskIdWithMediaRow.Key) + .ToDictionary(originalDiskIdWithMediaRow => originalDiskIdWithMediaRow.Key, originalDiskIdWithMediaRow => originalDiskIdWithMediaRow.Value.DiskId); + + // Update the File row and FileSymbols so the DiskIds are correct in the WixOutput, even if this + // data doesn't show up in the Windows Installer database. + foreach (var fileRow in fileRows) + { + if (oldDiskIdToNewDiskId.TryGetValue(fileRow.DiskId, out var newDiskId)) + { + fileRow.DiskId = newDiskId; + } + } + + foreach (var fileSymbol in this.Section.Symbols.OfType()) + { + if (fileSymbol.DiskId.HasValue && oldDiskIdToNewDiskId.TryGetValue(fileSymbol.DiskId.Value, out var newDiskId)) + { + fileSymbol.DiskId = newDiskId; + } + } + + // Update the MediaSymbol DiskIds to the correct DiskId. Note that the MediaSymbol Id + // is not changed because symbol ids are not allowed to change after they are created. + foreach (var mediaSymbol in this.Section.Symbols.OfType()) + { + if (oldDiskIdToNewDiskId.TryGetValue(mediaSymbol.DiskId, out var newDiskId)) + { + mediaSymbol.DiskId = newDiskId; + } + } + + // Now that the existing MediaSymbol DiskIds are updated, add the newly created Media rows + // as symbols. Notice that the new MediaSymbols do not have an Id because they very likely + // would conflict with MediaSymbols that had their DiskIds updated but Ids could not be updated. + // The newly created MediaSymbols will rename anonymous. + foreach (var mediaRow in addedMediaRows) + { + this.Section.AddSymbol(new MediaSymbol(mediaRow.SourceLineNumbers) + { + Cabinet = mediaRow.Cabinet, + DiskId = mediaRow.DiskId, + DiskPrompt = mediaRow.DiskPrompt, + LastSequence = mediaRow.LastSequence, + Source = mediaRow.Source, + VolumeLabel = mediaRow.VolumeLabel + }); + } + } } } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs index 9aad3537..6662f8f7 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs @@ -17,30 +17,38 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// internal class ProcessUncompressedFilesCommand { - public ProcessUncompressedFilesCommand(IntermediateSection section, IBackendHelper backendHelper, IPathResolver pathResolver) + public ProcessUncompressedFilesCommand(IntermediateSection section, IBackendHelper backendHelper, IPathResolver pathResolver, IEnumerable fileFacades, string outputPath, bool compressed, bool longNamesInImage, Func resolveMedia) { this.Section = section; this.BackendHelper = backendHelper; this.PathResolver = pathResolver; + + this.DatabasePath = outputPath; + this.LayoutDirectory = Path.GetDirectoryName(outputPath); + this.Compressed = compressed; + this.LongNamesInImage = longNamesInImage; + + this.FileFacades = fileFacades; + this.ResolveMedia = resolveMedia; } private IntermediateSection Section { get; } - public IBackendHelper BackendHelper { get; } + private IBackendHelper BackendHelper { get; } - public IPathResolver PathResolver { get; } + private IPathResolver PathResolver { get; } - public string DatabasePath { private get; set; } + private string DatabasePath { get; } - public IEnumerable FileFacades { private get; set; } + private string LayoutDirectory { get; } - public string LayoutDirectory { private get; set; } + private bool Compressed { get; } - public bool Compressed { private get; set; } + private bool LongNamesInImage { get; } - public bool LongNamesInImage { private get; set; } + private IEnumerable FileFacades { get; } - public Func ResolveMedia { private get; set; } + private Func ResolveMedia { get; } public IEnumerable FileTransfers { get; private set; } diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs b/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs index f85d4842..65043658 100644 --- a/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs +++ b/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs @@ -167,6 +167,9 @@ namespace WixToolset.Core.ExtensibilityServices /// /// Allows direct access to the underlying FileRow as requried for patching. /// - public FileRow GetFileRow() => this.FileRow ?? throw new NotImplementedException(); + public FileRow GetFileRow() + { + return this.FileRow ?? throw new NotImplementedException(); + } } } diff --git a/src/wix/test/WixToolsetTest.Core.Native/CabinetFixture.cs b/src/wix/test/WixToolsetTest.Core.Native/CabinetFixture.cs index c566339a..e1189549 100644 --- a/src/wix/test/WixToolsetTest.Core.Native/CabinetFixture.cs +++ b/src/wix/test/WixToolsetTest.Core.Native/CabinetFixture.cs @@ -2,6 +2,7 @@ namespace WixToolsetTest.CoreNative { + using System; using System.IO; using System.Linq; using WixBuildTools.TestSupport; @@ -19,12 +20,45 @@ namespace WixToolsetTest.CoreNative var intermediateFolder = fs.GetFolder(true); var cabPath = Path.Combine(intermediateFolder, "testout.cab"); - var files = new[] { new CabinetCompressFile(TestData.Get(@"TestData\test.txt"), "test.txt") }; + var files = new[] { new CabinetCompressFile(TestData.Get(@"TestData", "test.txt"), "test.txt") }; var cabinet = new Cabinet(cabPath); - cabinet.Compress(files, CompressionLevel.Low); + var created = cabinet.Compress(files, CompressionLevel.Low); Assert.True(File.Exists(cabPath)); + Assert.Equal(new[] + { + "testout.cab, test.txt" + }, created.Select(c => String.Join(", ", c.CabinetName, c.FirstFileToken)).ToArray()); + } + } + + [Fact] + public void CanCreateSpannedFileCabinet() + { + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(true); + + // Put more than non-zero bytes in a file sized just under 3MB since there is + // some overhead in cabinets that prevent perfect packing on the megabyte boundary. + var threeMBPath = Path.Combine(intermediateFolder, "_3mb.dat"); + TestData.CreateFile(threeMBPath, (long)(2.9 * 1024 * 1024), fill: true); + + var cabPath = Path.Combine(intermediateFolder, "test.cab"); + + var files = new[] { new CabinetCompressFile(threeMBPath, Path.GetFileNameWithoutExtension(threeMBPath)) }; + + var cabinet = new Cabinet(cabPath); + var created = cabinet.Compress(files, CompressionLevel.None, maxSize: 1); + + Assert.True(File.Exists(cabPath)); + Assert.Equal(new[] + { + "test.cab, _3mb", + "testa.cab, _3mb", + "testb.cab, _3mb" + }, created.Select(c => String.Join(", ", c.CabinetName, c.FirstFileToken)).ToArray()); } } @@ -58,8 +92,8 @@ namespace WixToolsetTest.CoreNative // Compress. { var files = new[] { - new CabinetCompressFile(TestData.Get(@"TestData\test.txt"), "test1.txt"), - new CabinetCompressFile(TestData.Get(@"TestData\test.txt"), "test2.txt"), + new CabinetCompressFile(TestData.Get(@"TestData", "test.txt"), "test1.txt"), + new CabinetCompressFile(TestData.Get(@"TestData", "test.txt"), "test2.txt"), }; var cabinet = new Cabinet(cabinetPath); diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsiCabinetFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsiCabinetFixture.cs new file mode 100644 index 00000000..8bb790d4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsiCabinetFixture.cs @@ -0,0 +1,256 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using Xunit; + + public class MsiCabinetFixture + { + [Fact] + public void CanBuildSingleFileCompressed() + { + var folder = TestData.Get(@"TestData\SingleFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().Single(); + WixAssert.StringEqual(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + WixAssert.StringEqual(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + } + + [Fact] + public void CanBuildSingleFileCompressedWithMediaTemplate() + { + var folder = TestData.Get(@"TestData\SingleFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanBuildSingleFileCompressedWithMediaTemplateWithLowCompression() + { + var folder = TestData.Get(@"TestData\SingleFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel=low", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\low1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanBuildMultipleFilesCompressed() + { + var folder = TestData.Get(@"TestData\MultiFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + "-sw1079", // TODO: why does this test need to create a second cab which is empty? + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, "bin", "test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, "bin", "example1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, "bin", "example2.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, "bin", "test.wixpdb"))); + } + } + + [Fact] + public void CanBuildMultipleFilesSpanningCabinets() + { + var folder = TestData.Get(@"TestData", "MsiCabinet"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var dataFolder = Path.Combine(folder, "data"); + var gendataFolder = Path.Combine(baseFolder, "generated-data"); + var cabFolder = Path.Combine(baseFolder, "cab"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var msiPath = Path.Combine(binFolder, "test.msi"); + var wixpdbPath = Path.ChangeExtension(msiPath, "wixpdb"); + + TestData.CreateFile(Path.Combine(gendataFolder, "abc.gen"), (long)(25 * 1024 * 1024), fill: true); + TestData.CreateFile(Path.Combine(gendataFolder, "mno.gen"), (long)(45 * 1024 * 1024), fill: true); + TestData.CreateFile(Path.Combine(gendataFolder, "xyz.gen"), (long)(25 * 1024 * 1024), fill: true); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MultiFileSpanningCabinets.wxs"), + "-bindpath", dataFolder, + "-bindpath", gendataFolder, + "-intermediateFolder", intermediateFolder, + "-cc", cabFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var files = Directory.GetFiles(binFolder, "*.*", SearchOption.AllDirectories).Select(s => s.Substring(binFolder.Length + 1)).OrderBy(s => s).ToArray(); + WixAssert.CompareLineByLine(new[] + { + "cab1.cab", + "cab1a.cab", + "cab2.cab", + "cab3.cab", + "cab3a.cab", + "cab3b.cab", + "cab4.cab", + "cab5.cab", + "cab5a.cab", + "test.msi", + "test.wixpdb" + }, files); + + var query = Query.QueryDatabase(msiPath, new[] { "Media", "File" }); + WixAssert.CompareLineByLine(new[] + { + "File:fil2WOk5jeIBsvmL0db5z96JfeEZoU\tfil2WOk5jeIBsvmL0db5z96JfeEZoU\thij.txt\t18\t\t\t512\t3", + "File:filgErUV04C8ZBKWWWA0Zg5Fu_6NyM\tfilgErUV04C8ZBKWWWA0Zg5Fu_6NyM\tmno.gen\t47185920\t\t\t512\t4", + "File:filGqbzmUDXsQhijNpBL2rNX3dtCoo\tfilGqbzmUDXsQhijNpBL2rNX3dtCoo\tced.txt\t18\t\t\t512\t2", + "File:filj2uZN0Q5bCgR2YY6RRg1c9FqylQ\tfilj2uZN0Q5bCgR2YY6RRg1c9FqylQ\ttuv.txt\t18\t\t\t512\t6", + "File:filjEKfcIaBHXFyDRtZciPv4j105jQ\tfiljEKfcIaBHXFyDRtZciPv4j105jQ\tqrs.txt\t18\t\t\t512\t5", + "File:filKv93aSvdbL6M6UutiKdGim1UzcA\tfilKv93aSvdbL6M6UutiKdGim1UzcA\tabc.gen\t26214400\t\t\t512\t1", + "File:fillf1kS2G7fDKwbyrQIIw1OXPi.eY\tfillf1kS2G7fDKwbyrQIIw1OXPi.eY\txyz.gen\t26214400\t\t\t512\t7", + "Media:1\t1\t\tcab1.cab\t\t", + "Media:2\t1\t\tcab1a.cab\t\t", + "Media:3\t3\t\tcab2.cab\t\t", + "Media:4\t4\t\tcab3.cab\t\t", + "Media:5\t4\t\tcab3a.cab\t\t", + "Media:6\t4\t\tcab3b.cab\t\t", + "Media:7\t6\t\tcab4.cab\t\t", + "Media:8\t7\t\tcab5.cab\t\t", + "Media:9\t7\t\tcab5a.cab\t\t", + }, query); + + var wixpdb = WixOutput.Read(wixpdbPath); + + var data = WindowsInstallerData.Load(wixpdb); + var fileRows = data.Tables["File"].Rows.Cast().OrderBy(f => f.Sequence); + var mediaRows = data.Tables["Media"].Rows.Cast().OrderBy(f => f.DiskId); + + var intermediate = Intermediate.Load(wixpdb); + var section = intermediate.Sections.Single(); + var fileSymbols = section.Symbols.OfType().OrderBy(f => f.Sequence); + var mediaSymbols = section.Symbols.OfType().OrderBy(f => f.DiskId); + + var expectedFiles = new[] + { + "filKv93aSvdbL6M6UutiKdGim1UzcA abc.gen 1 1", + "filGqbzmUDXsQhijNpBL2rNX3dtCoo ced.txt 2 3", + "fil2WOk5jeIBsvmL0db5z96JfeEZoU hij.txt 3 3", + "filgErUV04C8ZBKWWWA0Zg5Fu_6NyM mno.gen 4 4", + "filjEKfcIaBHXFyDRtZciPv4j105jQ qrs.txt 5 7", + "filj2uZN0Q5bCgR2YY6RRg1c9FqylQ tuv.txt 6 7", + "fillf1kS2G7fDKwbyrQIIw1OXPi.eY xyz.gen 7 8", + }; + + var expectedMedia = new[] + { + "1 cab1.cab 1", + "2 cab1a.cab 1", + "3 cab2.cab 3", + "4 cab3.cab 4", + "5 cab3a.cab 4", + "6 cab3b.cab 4", + "7 cab4.cab 6", + "8 cab5.cab 7", + "9 cab5a.cab 7", + }; + + WixAssert.CompareLineByLine(expectedFiles, fileRows.Select(r => String.Join(" ", r.File, r.FileName, r.Sequence, r.DiskId)).ToArray()); + + WixAssert.CompareLineByLine(expectedFiles, fileSymbols.Select(s => String.Join(" ", s.Id.Id, s.Name, s.Sequence, s.DiskId)).ToArray()); + + WixAssert.CompareLineByLine(expectedMedia, mediaRows.Select(r => String.Join(" ", r.DiskId, r.Cabinet, r.LastSequence)).ToArray()); + + WixAssert.CompareLineByLine(expectedMedia, mediaSymbols.Select(s => String.Join(" ", s.DiskId, s.Cabinet, s.LastSequence)).ToArray()); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index 0c01fa5e..60e9653a 100644 --- a/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -58,130 +58,7 @@ namespace WixToolsetTest.CoreIntegration } [Fact] - public void CanBuildSingleFileCompressed() - { - var folder = TestData.Get(@"TestData\SingleFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().Single(); - WixAssert.StringEqual(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - WixAssert.StringEqual(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - } - - [Fact] - public void CanBuildSingleFileCompressedWithMediaTemplate() - { - var folder = TestData.Get(@"TestData\SingleFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-d", "MediaTemplateCompressionLevel", - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanBuildSingleFileCompressedWithMediaTemplateWithLowCompression() - { - var folder = TestData.Get(@"TestData\SingleFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-d", "MediaTemplateCompressionLevel=low", - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\low1.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanBuildMultipleFilesCompressed() - { - var folder = TestData.Get(@"TestData\MultiFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - "-sw1079", // TODO: why does this test need to create a second cab which is empty? - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example1.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example2.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanFailBuildMissingFile() + public void CannotBuildMissingFile() { var folder = TestData.Get(@"TestData\SingleFile"); diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/MultiFileSpanningCabinets.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/MultiFileSpanningCabinets.wxs new file mode 100644 index 00000000..c7ac4699 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/MultiFileSpanningCabinets.wxs @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/ced.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/ced.txt new file mode 100644 index 00000000..6f52fa6a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/ced.txt @@ -0,0 +1 @@ +This is ced.txt. diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/hij.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/hij.txt new file mode 100644 index 00000000..ca53b0ae --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/hij.txt @@ -0,0 +1 @@ +This is hij.txt. diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/qrs.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/qrs.txt new file mode 100644 index 00000000..8e33dd06 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/qrs.txt @@ -0,0 +1 @@ +This is qrs.txt. diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/tuv.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/tuv.txt new file mode 100644 index 00000000..3c258bd2 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/tuv.txt @@ -0,0 +1 @@ +This is tuv.txt. diff --git a/src/wix/wixnative/smartcab.cpp b/src/wix/wixnative/smartcab.cpp index b5f20736..9c441a34 100644 --- a/src/wix/wixnative/smartcab.cpp +++ b/src/wix/wixnative/smartcab.cpp @@ -2,8 +2,8 @@ #include "precomp.h" -static HRESULT CompressFiles(__in HANDLE hCab); -static void __stdcall CabNamesCallback(__in_z LPWSTR wzFirstCabName, __in_z LPWSTR wzNewCabName, __in_z LPWSTR wzFileToken); +static HRESULT CompressFiles(__in HANDLE hCab, __inout_z LPWSTR* psczFirstFileToken); +static void __stdcall CabNamesCallback(__in_z LPCWSTR wzFirstCabName, __in_z LPCWSTR wzNewCabName, __in_z LPCWSTR wzFileToken); HRESULT SmartCabCommand( @@ -20,6 +20,7 @@ HRESULT SmartCabCommand( UINT uiMaxThresh = 0; COMPRESSION_TYPE ct = COMPRESSION_TYPE_NONE; HANDLE hCab = NULL; + LPWSTR sczFirstFileToken = NULL; if (argc < 1) { @@ -71,16 +72,22 @@ HRESULT SmartCabCommand( hr = WixNativeReadStdinPreamble(); ExitOnFailure(hr, "failed to read stdin preamble before smartcabbing"); - hr = CompressFiles(hCab); + hr = CompressFiles(hCab, &sczFirstFileToken); ExitOnFailure(hr, "failed to compress files into cabinet: %ls", sczCabPath); + + CabNamesCallback(wzCabName, wzCabName, sczFirstFileToken); + } + else + { + CabNamesCallback(wzCabName, wzCabName, L""); } hr = CabCFinish(hCab, CabNamesCallback); hCab = NULL; // once finish is called, the handle is invalid. ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to compress cabinet: %ls", sczCabPath); - LExit: + ReleaseStr(sczFirstFileToken); if (hCab) { CabCCancel(hCab); @@ -93,7 +100,8 @@ LExit: static HRESULT CompressFiles( - __in HANDLE hCab + __in HANDLE hCab, + __inout_z LPWSTR* psczFirstFileToken ) { HRESULT hr = S_OK; @@ -138,6 +146,12 @@ static HRESULT CompressFiles( pHashInfo = &hashInfo; } + if (psczFirstFileToken && !*psczFirstFileToken) + { + hr = StrAllocString(psczFirstFileToken, wzToken, 0); + ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to allocate first file token: %ls", wzToken); + } + hr = CabCAddFile(wzFilePath, wzToken, pHashInfo, hCab); ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to add file: %ls", wzFilePath); @@ -157,9 +171,9 @@ LExit: // Second argument is name of the new cabinet that would be formed by splitting e.g. "cab1b.cab" // Third argument is the file token of the first file present in the splitting cabinet static void __stdcall CabNamesCallback( - __in_z LPWSTR wzFirstCabName, - __in_z LPWSTR wzNewCabName, - __in_z LPWSTR wzFileToken + __in_z LPCWSTR wzFirstCabName, + __in_z LPCWSTR wzNewCabName, + __in_z LPCWSTR wzFileToken ) { ConsoleWriteLine(CONSOLE_COLOR_NORMAL, "%ls\t%ls\t%ls", wzFirstCabName, wzNewCabName, wzFileToken); -- cgit v1.2.3-55-g6feb