aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs314
1 files changed, 314 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs
new file mode 100644
index 00000000..23c481b7
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs
@@ -0,0 +1,314 @@
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.Core.WindowsInstaller.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using WixToolset.Core.Bind;
9 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11
12 /// <summary>
13 /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows.
14 /// </summary>
15 public class AssignMediaCommand
16 {
17 public AssignMediaCommand()
18 {
19 this.CabinetNameTemplate = "Cab{0}.cab";
20 }
21
22 public Output Output { private get; set; }
23
24 public bool FilesCompressed { private get; set; }
25
26 public string CabinetNameTemplate { private get; set; }
27
28 public IEnumerable<FileFacade> FileFacades { private get; set; }
29
30 public TableDefinitionCollection TableDefinitions { private get; set; }
31
32 /// <summary>
33 /// Gets cabinets with their file rows.
34 /// </summary>
35 public Dictionary<MediaRow, IEnumerable<FileFacade>> FileFacadesByCabinetMedia { get; private set; }
36
37 /// <summary>
38 /// Get media rows.
39 /// </summary>
40 public RowDictionary<MediaRow> MediaRows { get; private set; }
41
42 /// <summary>
43 /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no.
44 /// This contains all the files when Package element is marked with compression=no
45 /// </summary>
46 public IEnumerable<FileFacade> UncompressedFileFacades { get; private set; }
47
48 public void Execute()
49 {
50 Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia = new Dictionary<MediaRow, List<FileFacade>>();
51
52 RowDictionary<MediaRow> mediaRows = new RowDictionary<MediaRow>();
53
54 List<FileFacade> uncompressedFiles = new List<FileFacade>();
55
56 MediaRow mergeModuleMediaRow = null;
57 Table mediaTable = this.Output.Tables["Media"];
58 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
59
60 // If both tables are authored, it is an error.
61 if ((mediaTemplateTable != null && mediaTemplateTable.Rows.Count > 0) && (mediaTable != null && mediaTable.Rows.Count > 1))
62 {
63 throw new WixException(WixErrors.MediaTableCollision(null));
64 }
65
66 // When building merge module, all the files go to "#MergeModule.CABinet".
67 if (OutputType.Module == this.Output.Type)
68 {
69 Table mergeModuleMediaTable = new Table(null, this.TableDefinitions["Media"]);
70 mergeModuleMediaRow = (MediaRow)mergeModuleMediaTable.CreateRow(null);
71 mergeModuleMediaRow.Cabinet = "#MergeModule.CABinet";
72
73 filesByCabinetMedia.Add(mergeModuleMediaRow, new List<FileFacade>());
74 }
75
76 if (OutputType.Module == this.Output.Type || null == mediaTemplateTable)
77 {
78 this.ManuallyAssignFiles(mediaTable, mergeModuleMediaRow, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles);
79 }
80 else
81 {
82 this.AutoAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles);
83 }
84
85 this.FileFacadesByCabinetMedia = new Dictionary<MediaRow, IEnumerable<FileFacade>>();
86
87 foreach (var mediaRowWithFiles in filesByCabinetMedia)
88 {
89 this.FileFacadesByCabinetMedia.Add(mediaRowWithFiles.Key, mediaRowWithFiles.Value);
90 }
91
92 this.MediaRows = mediaRows;
93
94 this.UncompressedFileFacades = uncompressedFiles;
95 }
96
97 /// <summary>
98 /// Assign files to cabinets based on MediaTemplate authoring.
99 /// </summary>
100 /// <param name="fileFacades">FileRowCollection</param>
101 private void AutoAssignFiles(Table mediaTable, IEnumerable<FileFacade> fileFacades, Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia, RowDictionary<MediaRow> mediaRows, List<FileFacade> uncompressedFiles)
102 {
103 const int MaxCabIndex = 999;
104
105 ulong currentPreCabSize = 0;
106 ulong maxPreCabSizeInBytes;
107 int maxPreCabSizeInMB = 0;
108 int currentCabIndex = 0;
109
110 MediaRow currentMediaRow = null;
111
112 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
113
114 // Auto assign files to cabinets based on maximum uncompressed media size
115 mediaTable.Rows.Clear();
116 WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0];
117
118 if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate))
119 {
120 this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate;
121 }
122
123 string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
124
125 try
126 {
127 // Override authored mums value if environment variable is authored.
128 if (!String.IsNullOrEmpty(mumsString))
129 {
130 maxPreCabSizeInMB = Int32.Parse(mumsString);
131 }
132 else
133 {
134 maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize;
135 }
136
137 maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024;
138 }
139 catch (FormatException)
140 {
141 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString));
142 }
143 catch (OverflowException)
144 {
145 throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB));
146 }
147
148 foreach (FileFacade facade in this.FileFacades)
149 {
150 // When building a product, if the current file is not to be compressed or if
151 // the package set not to be compressed, don't cab it.
152 if (OutputType.Product == this.Output.Type &&
153 (YesNoType.No == facade.File.Compressed ||
154 (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed)))
155 {
156 uncompressedFiles.Add(facade);
157 continue;
158 }
159
160 if (currentCabIndex == MaxCabIndex)
161 {
162 // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore.
163 List<FileFacade> cabinetFiles = filesByCabinetMedia[currentMediaRow];
164 facade.WixFile.DiskId = currentCabIndex;
165 cabinetFiles.Add(facade);
166 continue;
167 }
168
169 // Update current cab size.
170 currentPreCabSize += (ulong)facade.File.FileSize;
171
172 if (currentPreCabSize > maxPreCabSizeInBytes)
173 {
174 // Overflow due to current file
175 currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex);
176 mediaRows.Add(currentMediaRow);
177 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
178
179 List<FileFacade> cabinetFileRows = filesByCabinetMedia[currentMediaRow];
180 facade.WixFile.DiskId = currentCabIndex;
181 cabinetFileRows.Add(facade);
182 // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize
183 currentPreCabSize = (ulong)facade.File.FileSize;
184 }
185 else
186 {
187 // File fits in the current cab.
188 if (currentMediaRow == null)
189 {
190 // Create new cab and MediaRow
191 currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex);
192 mediaRows.Add(currentMediaRow);
193 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
194 }
195
196 // Associate current file with current cab.
197 List<FileFacade> cabinetFiles = filesByCabinetMedia[currentMediaRow];
198 facade.WixFile.DiskId = currentCabIndex;
199 cabinetFiles.Add(facade);
200 }
201 }
202
203 // If there are uncompressed files and no MediaRow, create a default one.
204 if (uncompressedFiles.Count > 0 && mediaTable.Rows.Count == 0)
205 {
206 MediaRow defaultMediaRow = (MediaRow)mediaTable.CreateRow(null);
207 defaultMediaRow.DiskId = 1;
208 mediaRows.Add(defaultMediaRow);
209 }
210 }
211
212 /// <summary>
213 /// Assign files to cabinets based on Media authoring.
214 /// </summary>
215 /// <param name="mediaTable"></param>
216 /// <param name="mergeModuleMediaRow"></param>
217 /// <param name="fileFacades"></param>
218 private void ManuallyAssignFiles(Table mediaTable, MediaRow mergeModuleMediaRow, IEnumerable<FileFacade> fileFacades, Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia, RowDictionary<MediaRow> mediaRows, List<FileFacade> uncompressedFiles)
219 {
220 if (OutputType.Module != this.Output.Type)
221 {
222 if (null != mediaTable)
223 {
224 Dictionary<string, MediaRow> cabinetMediaRows = new Dictionary<string, MediaRow>(StringComparer.InvariantCultureIgnoreCase);
225 foreach (MediaRow mediaRow in mediaTable.Rows)
226 {
227 // If the Media row has a cabinet, make sure it is unique across all Media rows.
228 if (!String.IsNullOrEmpty(mediaRow.Cabinet))
229 {
230 MediaRow existingRow;
231 if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out existingRow))
232 {
233 Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet));
234 Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet));
235 }
236 else
237 {
238 cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow);
239 }
240 }
241
242 mediaRows.Add(mediaRow);
243 }
244 }
245
246 foreach (MediaRow mediaRow in mediaRows.Values)
247 {
248 if (null != mediaRow.Cabinet)
249 {
250 filesByCabinetMedia.Add(mediaRow, new List<FileFacade>());
251 }
252 }
253 }
254
255 foreach (FileFacade facade in fileFacades)
256 {
257 if (OutputType.Module == this.Output.Type)
258 {
259 filesByCabinetMedia[mergeModuleMediaRow].Add(facade);
260 }
261 else
262 {
263 MediaRow mediaRow;
264 if (!mediaRows.TryGetValue(facade.WixFile.DiskId.ToString(CultureInfo.InvariantCulture), out mediaRow))
265 {
266 Messaging.Instance.OnMessage(WixErrors.MissingMedia(facade.File.SourceLineNumbers, facade.WixFile.DiskId));
267 continue;
268 }
269
270 // When building a product, if the current file is not to be compressed or if
271 // the package set not to be compressed, don't cab it.
272 if (OutputType.Product == this.Output.Type &&
273 (YesNoType.No == facade.File.Compressed ||
274 (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed)))
275 {
276 uncompressedFiles.Add(facade);
277 }
278 else // file is marked compressed.
279 {
280 List<FileFacade> cabinetFiles;
281 if (filesByCabinetMedia.TryGetValue(mediaRow, out cabinetFiles))
282 {
283 cabinetFiles.Add(facade);
284 }
285 else
286 {
287 Messaging.Instance.OnMessage(WixErrors.ExpectedMediaCabinet(facade.File.SourceLineNumbers, facade.File.File, facade.WixFile.DiskId));
288 }
289 }
290 }
291 }
292 }
293
294 /// <summary>
295 /// Adds a row to the media table with cab name template filled in.
296 /// </summary>
297 /// <param name="mediaTable"></param>
298 /// <param name="cabIndex"></param>
299 /// <returns></returns>
300 private MediaRow AddMediaRow(WixMediaTemplateRow mediaTemplateRow, Table mediaTable, int cabIndex)
301 {
302 MediaRow currentMediaRow = (MediaRow)mediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers);
303 currentMediaRow.DiskId = cabIndex;
304 currentMediaRow.Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex);
305
306 Table wixMediaTable = this.Output.EnsureTable(this.TableDefinitions["WixMedia"]);
307 WixMediaRow row = (WixMediaRow)wixMediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers);
308 row.DiskId = cabIndex;
309 row.CompressionLevel = mediaTemplateRow.CompressionLevel;
310
311 return currentMediaRow;
312 }
313 }
314}