main
cs 302 lines 12.8 KB
Raw
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
3 namespace WixToolset.Core.WindowsInstaller.Bind
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 /// <summary>
15 /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows.
16 /// </summary>
17 internal class AssignMediaCommand
18 {
19 private const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB
20
21 public AssignMediaCommand(IntermediateSection section, IMessaging messaging, IEnumerable<IFileFacade> fileFacades, bool compressed)
22 {
23 this.CabinetNameTemplate = "Cab{0}.cab";
24 this.Section = section;
25 this.Messaging = messaging;
26 this.FileFacades = fileFacades;
27 this.FilesCompressed = compressed;
28 }
29
30 private IntermediateSection Section { get; }
31
32 private IMessaging Messaging { get; }
33
34 private IEnumerable<IFileFacade> FileFacades { get; }
35
36 private bool FilesCompressed { get; }
37
38 private string CabinetNameTemplate { get; set; }
39
40 /// <summary>
41 /// Gets cabinets with their file rows.
42 /// </summary>
43 public Dictionary<MediaSymbol, IEnumerable<IFileFacade>> FileFacadesByCabinetMedia { get; private set; }
44
45 /// <summary>
46 /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no.
47 /// This contains all the files when Package element is marked with compression=no
48 /// </summary>
49 public IEnumerable<IFileFacade> UncompressedFileFacades { get; private set; }
50
51 public void Execute()
52 {
53 var mediaSymbols = this.Section.Symbols.OfType<MediaSymbol>().ToList();
54 var mediaTemplateSymbols = this.Section.Symbols.OfType<WixMediaTemplateSymbol>().ToList();
55
56 // If both symbols are authored, it is an error.
57 if (mediaTemplateSymbols.Count > 0 && mediaSymbols.Count > 1)
58 {
59 throw new WixException(ErrorMessages.MediaTableCollision(null));
60 }
61
62 // If neither symbol is authored, default to a media template.
63 if (SectionType.Package == this.Section.Type && mediaTemplateSymbols.Count == 0 && mediaSymbols.Count == 0)
64 {
65 var mediaTemplate = new WixMediaTemplateSymbol()
66 {
67 CabinetTemplate = "cab{0}.cab",
68 };
69
70 this.Section.AddSymbol(mediaTemplate);
71 mediaTemplateSymbols.Add(mediaTemplate);
72 }
73
74 // When building merge module, all the files go to "#MergeModule.CABinet".
75 if (SectionType.Module == this.Section.Type)
76 {
77 var mergeModuleMediaSymbol = this.Section.AddSymbol(new MediaSymbol
78 {
79 Cabinet = "#MergeModule.CABinet",
80 });
81
82 this.FileFacadesByCabinetMedia = new Dictionary<MediaSymbol, IEnumerable<IFileFacade>>
83 {
84 { mergeModuleMediaSymbol, this.FileFacades }
85 };
86
87 this.UncompressedFileFacades = Array.Empty<IFileFacade>();
88 }
89 else
90 {
91 var filesByCabinetMedia = new Dictionary<MediaSymbol, List<IFileFacade>>();
92 var uncompressedFiles = new List<IFileFacade>();
93
94 if (mediaTemplateSymbols.Count > 0)
95 {
96 this.AutoAssignFiles(mediaTemplateSymbols, mediaSymbols, filesByCabinetMedia, uncompressedFiles);
97 }
98 else
99 {
100 this.ManuallyAssignFiles(mediaSymbols, filesByCabinetMedia, uncompressedFiles);
101 }
102
103 this.FileFacadesByCabinetMedia = filesByCabinetMedia.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable<IFileFacade>)kvp.Value);
104
105 this.UncompressedFileFacades = uncompressedFiles;
106 }
107 }
108
109 /// <summary>
110 /// Assign files to cabinets based on MediaTemplate authoring.
111 /// </summary>
112 private void AutoAssignFiles(List<WixMediaTemplateSymbol> mediaTemplateTable, List<MediaSymbol> mediaSymbols, Dictionary<MediaSymbol, List<IFileFacade>> filesByCabinetMedia, List<IFileFacade> uncompressedFiles)
113 {
114 const int MaxCabIndex = 999;
115
116 ulong currentPreCabSize = 0;
117 ulong maxPreCabSizeInBytes;
118 var maxPreCabSizeInMB = 0;
119 var currentCabIndex = 0;
120
121 MediaSymbol currentMediaRow = null;
122
123 // Remove all previous media symbols since they will be replaced with
124 // media template.
125 foreach (var mediaSymbol in mediaSymbols)
126 {
127 this.Section.RemoveSymbol(mediaSymbol);
128 }
129
130 // Auto assign files to cabinets based on maximum uncompressed media size
131 var mediaTemplateRow = mediaTemplateTable.Single();
132
133 if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate))
134 {
135 this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate;
136 }
137
138 var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
139
140 try
141 {
142 // Override authored mums value if environment variable is authored.
143 if (!String.IsNullOrEmpty(mumsString))
144 {
145 maxPreCabSizeInMB = Int32.Parse(mumsString);
146 }
147 else
148 {
149 maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize;
150 }
151
152 maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024;
153 }
154 catch (FormatException)
155 {
156 throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MUMS", mumsString));
157 }
158 catch (OverflowException)
159 {
160 throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB));
161 }
162
163 var mediaSymbolsByDiskId = new Dictionary<int, MediaSymbol>();
164
165 foreach (var facade in this.FileFacades)
166 {
167 // When building a product, if the current file is not to be compressed or if
168 // the package set not to be compressed, don't cab it.
169 if (SectionType.Package == this.Section.Type && (facade.Uncompressed || !this.FilesCompressed))
170 {
171 uncompressedFiles.Add(facade);
172 continue;
173 }
174
175 if (currentCabIndex == MaxCabIndex)
176 {
177 // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore.
178 }
179 else
180 {
181 // Update current cab size.
182 currentPreCabSize += (ulong)facade.FileSize;
183
184 // Overflow due to current file
185 if (currentPreCabSize > maxPreCabSizeInBytes)
186 {
187 currentMediaRow = this.AddMediaSymbol(mediaTemplateRow, ++currentCabIndex);
188 mediaSymbolsByDiskId.Add(currentMediaRow.DiskId, currentMediaRow);
189 filesByCabinetMedia.Add(currentMediaRow, new List<IFileFacade>());
190
191 // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize
192 currentPreCabSize = (ulong)facade.FileSize;
193 }
194 else // file fits in the current cab.
195 {
196 if (currentMediaRow == null)
197 {
198 // Create new cab and MediaRow
199 currentMediaRow = this.AddMediaSymbol(mediaTemplateRow, ++currentCabIndex);
200 mediaSymbolsByDiskId.Add(currentMediaRow.DiskId, currentMediaRow);
201 filesByCabinetMedia.Add(currentMediaRow, new List<IFileFacade>());
202 }
203 }
204 }
205
206 // Associate current file with current cab.
207 var cabinetFiles = filesByCabinetMedia[currentMediaRow];
208 facade.DiskId = currentCabIndex;
209 cabinetFiles.Add(facade);
210 }
211
212 // If there are uncompressed files and no MediaRow, create a default one.
213 if (uncompressedFiles.Count > 0 && mediaSymbolsByDiskId.Count == 0)
214 {
215 var defaultMediaRow = this.Section.AddSymbol(new MediaSymbol(null, new Identifier(AccessModifier.Section, 1))
216 {
217 DiskId = 1,
218 });
219
220 mediaSymbolsByDiskId.Add(1, defaultMediaRow);
221 }
222 }
223
224 /// <summary>
225 /// Assign files to cabinets based on Media authoring.
226 /// </summary>
227 private void ManuallyAssignFiles(List<MediaSymbol> mediaSymbols, Dictionary<MediaSymbol, List<IFileFacade>> filesByCabinetMedia, List<IFileFacade> uncompressedFiles)
228 {
229 var mediaSymbolsByDiskId = new Dictionary<int, MediaSymbol>();
230
231 if (mediaSymbols.Any())
232 {
233 var cabinetMediaSymbols = new Dictionary<string, MediaSymbol>(StringComparer.OrdinalIgnoreCase);
234 foreach (var mediaSymbol in mediaSymbols)
235 {
236 // If the Media row has a cabinet, make sure it is unique across all Media rows.
237 if (!String.IsNullOrEmpty(mediaSymbol.Cabinet))
238 {
239 if (cabinetMediaSymbols.TryGetValue(mediaSymbol.Cabinet, out var existingRow))
240 {
241 this.Messaging.Write(ErrorMessages.DuplicateCabinetName(mediaSymbol.SourceLineNumbers, mediaSymbol.Cabinet));
242 this.Messaging.Write(ErrorMessages.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet));
243 }
244 else
245 {
246 cabinetMediaSymbols.Add(mediaSymbol.Cabinet, mediaSymbol);
247 }
248
249 filesByCabinetMedia.Add(mediaSymbol, new List<IFileFacade>());
250 }
251
252 mediaSymbolsByDiskId.Add(mediaSymbol.DiskId, mediaSymbol);
253 }
254 }
255
256 foreach (var facade in this.FileFacades)
257 {
258 if (!mediaSymbolsByDiskId.TryGetValue(facade.DiskId, out var mediaSymbol))
259 {
260 this.Messaging.Write(ErrorMessages.MissingMedia(facade.SourceLineNumber, facade.DiskId));
261 continue;
262 }
263
264 // When building a product, if the current file is to be uncompressed or if
265 // the package set not to be compressed, don't cab it.
266 var compressed = facade.Compressed;
267 var uncompressed = facade.Uncompressed;
268 if (SectionType.Package == this.Section.Type && (uncompressed || (!compressed && !this.FilesCompressed)))
269 {
270 uncompressedFiles.Add(facade);
271 }
272 else // file is marked compressed.
273 {
274 if (filesByCabinetMedia.TryGetValue(mediaSymbol, out var cabinetFiles))
275 {
276 cabinetFiles.Add(facade);
277 }
278 else
279 {
280 this.Messaging.Write(ErrorMessages.ExpectedMediaCabinet(facade.SourceLineNumber, facade.Id, facade.DiskId));
281 }
282 }
283 }
284 }
285
286 /// <summary>
287 /// Adds a symbol to the section with cab name template filled in.
288 /// </summary>
289 /// <param name="mediaTemplateSymbol"></param>
290 /// <param name="cabIndex"></param>
291 /// <returns></returns>
292 private MediaSymbol AddMediaSymbol(WixMediaTemplateSymbol mediaTemplateSymbol, int cabIndex)
293 {
294 return this.Section.AddSymbol(new MediaSymbol(mediaTemplateSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, cabIndex))
295 {
296 DiskId = cabIndex,
297 Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex),
298 CompressionLevel = mediaTemplateSymbol.CompressionLevel,
299 });
300 }
301 }
302 }