diff options
Diffstat (limited to '')
-rw-r--r-- | src/dtf/WixToolset.Dtf.Compression.Zip/ZipPacker.cs | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipPacker.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipPacker.cs new file mode 100644 index 00000000..b9c183d3 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipPacker.cs | |||
@@ -0,0 +1,489 @@ | |||
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.Dtf.Compression.Zip | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.IO.Compression; | ||
8 | using System.Collections.Generic; | ||
9 | using System.Globalization; | ||
10 | |||
11 | public partial class ZipEngine | ||
12 | { | ||
13 | /// <summary> | ||
14 | /// Creates a zip archive or chain of zip archives. | ||
15 | /// </summary> | ||
16 | /// <param name="streamContext">A context interface to handle opening | ||
17 | /// and closing of archive and file streams.</param> | ||
18 | /// <param name="files">An array of file lists. Each list is | ||
19 | /// compressed into one stream in the archive.</param> | ||
20 | /// <param name="maxArchiveSize">The maximum number of bytes for one archive | ||
21 | /// before the contents are chained to the next archive, or zero for unlimited | ||
22 | /// archive size.</param> | ||
23 | /// <exception cref="ArchiveException">The archive could not be | ||
24 | /// created.</exception> | ||
25 | /// <remarks> | ||
26 | /// The stream context implementation may provide a mapping from the file | ||
27 | /// paths within the archive to the external file paths. | ||
28 | /// </remarks> | ||
29 | public override void Pack( | ||
30 | IPackStreamContext streamContext, | ||
31 | IEnumerable<string> files, | ||
32 | long maxArchiveSize) | ||
33 | { | ||
34 | if (streamContext == null) | ||
35 | { | ||
36 | throw new ArgumentNullException("streamContext"); | ||
37 | } | ||
38 | |||
39 | if (files == null) | ||
40 | { | ||
41 | throw new ArgumentNullException("files"); | ||
42 | } | ||
43 | |||
44 | lock (this) | ||
45 | { | ||
46 | Stream archiveStream = null; | ||
47 | try | ||
48 | { | ||
49 | this.ResetProgressData(); | ||
50 | this.totalArchives = 1; | ||
51 | |||
52 | object forceZip64Value = streamContext.GetOption("forceZip64", null); | ||
53 | bool forceZip64 = Convert.ToBoolean( | ||
54 | forceZip64Value, CultureInfo.InvariantCulture); | ||
55 | |||
56 | // Count the total number of files and bytes to be compressed. | ||
57 | foreach (string file in files) | ||
58 | { | ||
59 | FileAttributes attributes; | ||
60 | DateTime lastWriteTime; | ||
61 | Stream fileStream = streamContext.OpenFileReadStream( | ||
62 | file, | ||
63 | out attributes, | ||
64 | out lastWriteTime); | ||
65 | if (fileStream != null) | ||
66 | { | ||
67 | this.totalFileBytes += fileStream.Length; | ||
68 | this.totalFiles++; | ||
69 | streamContext.CloseFileReadStream(file, fileStream); | ||
70 | } | ||
71 | } | ||
72 | |||
73 | List<ZipFileHeader> fileHeaders = new List<ZipFileHeader>(); | ||
74 | this.currentFileNumber = -1; | ||
75 | |||
76 | if (this.currentArchiveName == null) | ||
77 | { | ||
78 | this.mainArchiveName = streamContext.GetArchiveName(0); | ||
79 | this.currentArchiveName = this.mainArchiveName; | ||
80 | |||
81 | if (String.IsNullOrEmpty(this.currentArchiveName)) | ||
82 | { | ||
83 | throw new FileNotFoundException("No name provided for archive."); | ||
84 | } | ||
85 | } | ||
86 | |||
87 | this.OnProgress(ArchiveProgressType.StartArchive); | ||
88 | |||
89 | // Compress files one by one, saving header info for each. | ||
90 | foreach (string file in files) | ||
91 | { | ||
92 | ZipFileHeader fileHeader = this.PackOneFile( | ||
93 | streamContext, | ||
94 | file, | ||
95 | maxArchiveSize, | ||
96 | forceZip64, | ||
97 | ref archiveStream); | ||
98 | |||
99 | if (fileHeader != null) | ||
100 | { | ||
101 | fileHeaders.Add(fileHeader); | ||
102 | } | ||
103 | |||
104 | this.currentArchiveTotalBytes = (archiveStream != null ? | ||
105 | archiveStream.Position : 0); | ||
106 | this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes; | ||
107 | } | ||
108 | |||
109 | bool zip64 = forceZip64 || this.totalFiles > UInt16.MaxValue; | ||
110 | |||
111 | // Write the central directory composed of all the file headers. | ||
112 | uint centralDirStartArchiveNumber = 0; | ||
113 | long centralDirStartPosition = 0; | ||
114 | long centralDirSize = 0; | ||
115 | for (int i = 0; i < fileHeaders.Count; i++) | ||
116 | { | ||
117 | ZipFileHeader fileHeader = fileHeaders[i]; | ||
118 | |||
119 | int headerSize = fileHeader.GetSize(true); | ||
120 | centralDirSize += headerSize; | ||
121 | |||
122 | this.CheckArchiveWriteStream( | ||
123 | streamContext, | ||
124 | maxArchiveSize, | ||
125 | headerSize, | ||
126 | ref archiveStream); | ||
127 | |||
128 | if (i == 0) | ||
129 | { | ||
130 | centralDirStartArchiveNumber = (uint) this.currentArchiveNumber; | ||
131 | centralDirStartPosition = archiveStream.Position; | ||
132 | } | ||
133 | |||
134 | fileHeader.Write(archiveStream, true); | ||
135 | if (fileHeader.zip64) | ||
136 | { | ||
137 | zip64 = true; | ||
138 | } | ||
139 | } | ||
140 | |||
141 | this.currentArchiveTotalBytes = | ||
142 | (archiveStream != null ? archiveStream.Position : 0); | ||
143 | this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes; | ||
144 | |||
145 | ZipEndOfCentralDirectory eocd = new ZipEndOfCentralDirectory(); | ||
146 | eocd.dirStartDiskNumber = centralDirStartArchiveNumber; | ||
147 | eocd.entriesOnDisk = fileHeaders.Count; | ||
148 | eocd.totalEntries = fileHeaders.Count; | ||
149 | eocd.dirSize = centralDirSize; | ||
150 | eocd.dirOffset = centralDirStartPosition; | ||
151 | eocd.comment = this.comment; | ||
152 | |||
153 | Zip64EndOfCentralDirectoryLocator eocdl = | ||
154 | new Zip64EndOfCentralDirectoryLocator(); | ||
155 | |||
156 | int maxFooterSize = eocd.GetSize(false); | ||
157 | if (archiveStream != null && (zip64 || archiveStream.Position > | ||
158 | ((long) UInt32.MaxValue) - eocd.GetSize(false))) | ||
159 | { | ||
160 | maxFooterSize += eocd.GetSize(true) + (int) | ||
161 | Zip64EndOfCentralDirectoryLocator.EOCDL64_SIZE; | ||
162 | zip64 = true; | ||
163 | } | ||
164 | |||
165 | this.CheckArchiveWriteStream( | ||
166 | streamContext, | ||
167 | maxArchiveSize, | ||
168 | maxFooterSize, | ||
169 | ref archiveStream); | ||
170 | eocd.diskNumber = (uint) this.currentArchiveNumber; | ||
171 | |||
172 | if (zip64) | ||
173 | { | ||
174 | eocd.versionMadeBy = 45; | ||
175 | eocd.versionNeeded = 45; | ||
176 | eocd.zip64 = true; | ||
177 | eocdl.dirOffset = archiveStream.Position; | ||
178 | eocdl.dirStartDiskNumber = (uint) this.currentArchiveNumber; | ||
179 | eocdl.totalDisks = (uint) this.currentArchiveNumber + 1; | ||
180 | eocd.Write(archiveStream); | ||
181 | eocdl.Write(archiveStream); | ||
182 | |||
183 | eocd.dirOffset = UInt32.MaxValue; | ||
184 | eocd.dirStartDiskNumber = UInt16.MaxValue; | ||
185 | } | ||
186 | |||
187 | eocd.zip64 = false; | ||
188 | eocd.Write(archiveStream); | ||
189 | |||
190 | this.currentArchiveTotalBytes = archiveStream.Position; | ||
191 | this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes; | ||
192 | } | ||
193 | finally | ||
194 | { | ||
195 | if (archiveStream != null) | ||
196 | { | ||
197 | streamContext.CloseArchiveWriteStream( | ||
198 | this.currentArchiveNumber, this.mainArchiveName, archiveStream); | ||
199 | this.OnProgress(ArchiveProgressType.FinishArchive); | ||
200 | } | ||
201 | } | ||
202 | } | ||
203 | } | ||
204 | |||
205 | /// <summary> | ||
206 | /// Moves to the next archive in the sequence if necessary. | ||
207 | /// </summary> | ||
208 | private void CheckArchiveWriteStream( | ||
209 | IPackStreamContext streamContext, | ||
210 | long maxArchiveSize, | ||
211 | long requiredSize, | ||
212 | ref Stream archiveStream) | ||
213 | { | ||
214 | if (archiveStream != null && | ||
215 | archiveStream.Length > 0 && maxArchiveSize > 0) | ||
216 | { | ||
217 | long sizeRemaining = maxArchiveSize - archiveStream.Length; | ||
218 | if (sizeRemaining < requiredSize) | ||
219 | { | ||
220 | string nextArchiveName = streamContext.GetArchiveName( | ||
221 | this.currentArchiveNumber + 1); | ||
222 | |||
223 | if (String.IsNullOrEmpty(nextArchiveName)) | ||
224 | { | ||
225 | throw new FileNotFoundException("No name provided for archive #" + | ||
226 | this.currentArchiveNumber + 1); | ||
227 | } | ||
228 | |||
229 | this.currentArchiveTotalBytes = archiveStream.Position; | ||
230 | this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes; | ||
231 | |||
232 | streamContext.CloseArchiveWriteStream( | ||
233 | this.currentArchiveNumber, | ||
234 | nextArchiveName, | ||
235 | archiveStream); | ||
236 | archiveStream = null; | ||
237 | |||
238 | this.OnProgress(ArchiveProgressType.FinishArchive); | ||
239 | |||
240 | this.currentArchiveNumber++; | ||
241 | this.totalArchives++; | ||
242 | this.currentArchiveBytesProcessed = 0; | ||
243 | this.currentArchiveTotalBytes = 0; | ||
244 | } | ||
245 | } | ||
246 | |||
247 | if (archiveStream == null) | ||
248 | { | ||
249 | if (this.currentArchiveNumber > 0) | ||
250 | { | ||
251 | this.OnProgress(ArchiveProgressType.StartArchive); | ||
252 | } | ||
253 | |||
254 | archiveStream = streamContext.OpenArchiveWriteStream( | ||
255 | this.currentArchiveNumber, this.mainArchiveName, true, this); | ||
256 | |||
257 | if (archiveStream == null) | ||
258 | { | ||
259 | throw new FileNotFoundException("Stream not provided for archive #" + | ||
260 | this.currentArchiveNumber); | ||
261 | } | ||
262 | } | ||
263 | } | ||
264 | |||
265 | /// <summary> | ||
266 | /// Adds one file to a zip archive in the process of being created. | ||
267 | /// </summary> | ||
268 | private ZipFileHeader PackOneFile( | ||
269 | IPackStreamContext streamContext, | ||
270 | string file, | ||
271 | long maxArchiveSize, | ||
272 | bool forceZip64, | ||
273 | ref Stream archiveStream) | ||
274 | { | ||
275 | Stream fileStream = null; | ||
276 | int headerArchiveNumber = 0; | ||
277 | try | ||
278 | { | ||
279 | // TODO: call GetOption to get compression method for the specific file | ||
280 | ZipCompressionMethod compressionMethod = ZipCompressionMethod.Deflate; | ||
281 | if (this.CompressionLevel == WixToolset.Dtf.Compression.CompressionLevel.None) | ||
282 | { | ||
283 | compressionMethod = ZipCompressionMethod.Store; | ||
284 | } | ||
285 | |||
286 | Converter<Stream, Stream> compressionStreamCreator; | ||
287 | if (!ZipEngine.compressionStreamCreators.TryGetValue( | ||
288 | compressionMethod, out compressionStreamCreator)) | ||
289 | { | ||
290 | return null; | ||
291 | } | ||
292 | |||
293 | FileAttributes attributes; | ||
294 | DateTime lastWriteTime; | ||
295 | fileStream = streamContext.OpenFileReadStream( | ||
296 | file, out attributes, out lastWriteTime); | ||
297 | if (fileStream == null) | ||
298 | { | ||
299 | return null; | ||
300 | } | ||
301 | |||
302 | this.currentFileName = file; | ||
303 | this.currentFileNumber++; | ||
304 | |||
305 | this.currentFileTotalBytes = fileStream.Length; | ||
306 | this.currentFileBytesProcessed = 0; | ||
307 | this.OnProgress(ArchiveProgressType.StartFile); | ||
308 | |||
309 | ZipFileInfo fileInfo = new ZipFileInfo( | ||
310 | file, | ||
311 | this.currentArchiveNumber, | ||
312 | attributes, | ||
313 | lastWriteTime, | ||
314 | fileStream.Length, | ||
315 | 0, | ||
316 | compressionMethod); | ||
317 | |||
318 | bool zip64 = forceZip64 || fileStream.Length >= (long) UInt32.MaxValue; | ||
319 | ZipFileHeader fileHeader = new ZipFileHeader(fileInfo, zip64); | ||
320 | |||
321 | this.CheckArchiveWriteStream( | ||
322 | streamContext, | ||
323 | maxArchiveSize, | ||
324 | fileHeader.GetSize(false), | ||
325 | ref archiveStream); | ||
326 | |||
327 | long headerPosition = archiveStream.Position; | ||
328 | fileHeader.Write(archiveStream, false); | ||
329 | headerArchiveNumber = this.currentArchiveNumber; | ||
330 | |||
331 | uint crc; | ||
332 | long bytesWritten = this.PackFileBytes( | ||
333 | streamContext, | ||
334 | fileStream, | ||
335 | maxArchiveSize, | ||
336 | compressionStreamCreator, | ||
337 | ref archiveStream, | ||
338 | out crc); | ||
339 | |||
340 | fileHeader.Update( | ||
341 | bytesWritten, | ||
342 | fileStream.Length, | ||
343 | crc, | ||
344 | headerPosition, | ||
345 | headerArchiveNumber); | ||
346 | |||
347 | streamContext.CloseFileReadStream(file, fileStream); | ||
348 | fileStream = null; | ||
349 | |||
350 | // Go back and rewrite the updated file header. | ||
351 | if (this.currentArchiveNumber == headerArchiveNumber) | ||
352 | { | ||
353 | long fileEndPosition = archiveStream.Position; | ||
354 | archiveStream.Seek(headerPosition, SeekOrigin.Begin); | ||
355 | fileHeader.Write(archiveStream, false); | ||
356 | archiveStream.Seek(fileEndPosition, SeekOrigin.Begin); | ||
357 | } | ||
358 | else | ||
359 | { | ||
360 | // The file spanned archives, so temporarily reopen | ||
361 | // the archive where it started. | ||
362 | string headerArchiveName = streamContext.GetArchiveName( | ||
363 | headerArchiveNumber + 1); | ||
364 | Stream headerStream = null; | ||
365 | try | ||
366 | { | ||
367 | headerStream = streamContext.OpenArchiveWriteStream( | ||
368 | headerArchiveNumber, headerArchiveName, false, this); | ||
369 | headerStream.Seek(headerPosition, SeekOrigin.Begin); | ||
370 | fileHeader.Write(headerStream, false); | ||
371 | } | ||
372 | finally | ||
373 | { | ||
374 | if (headerStream != null) | ||
375 | { | ||
376 | streamContext.CloseArchiveWriteStream( | ||
377 | headerArchiveNumber, headerArchiveName, headerStream); | ||
378 | } | ||
379 | } | ||
380 | } | ||
381 | |||
382 | this.OnProgress(ArchiveProgressType.FinishFile); | ||
383 | |||
384 | return fileHeader; | ||
385 | } | ||
386 | finally | ||
387 | { | ||
388 | if (fileStream != null) | ||
389 | { | ||
390 | streamContext.CloseFileReadStream( | ||
391 | this.currentFileName, fileStream); | ||
392 | } | ||
393 | } | ||
394 | } | ||
395 | |||
396 | /// <summary> | ||
397 | /// Writes compressed bytes of one file to the archive, | ||
398 | /// keeping track of the CRC and number of bytes written. | ||
399 | /// </summary> | ||
400 | private long PackFileBytes( | ||
401 | IPackStreamContext streamContext, | ||
402 | Stream fileStream, | ||
403 | long maxArchiveSize, | ||
404 | Converter<Stream, Stream> compressionStreamCreator, | ||
405 | ref Stream archiveStream, | ||
406 | out uint crc) | ||
407 | { | ||
408 | long writeStartPosition = archiveStream.Position; | ||
409 | long bytesWritten = 0; | ||
410 | CrcStream fileCrcStream = new CrcStream(fileStream); | ||
411 | |||
412 | ConcatStream concatStream = new ConcatStream( | ||
413 | delegate(ConcatStream s) | ||
414 | { | ||
415 | Stream sourceStream = s.Source; | ||
416 | bytesWritten += sourceStream.Position - writeStartPosition; | ||
417 | |||
418 | this.CheckArchiveWriteStream( | ||
419 | streamContext, | ||
420 | maxArchiveSize, | ||
421 | 1, | ||
422 | ref sourceStream); | ||
423 | |||
424 | writeStartPosition = sourceStream.Position; | ||
425 | s.Source = sourceStream; | ||
426 | }); | ||
427 | |||
428 | concatStream.Source = archiveStream; | ||
429 | |||
430 | if (maxArchiveSize > 0) | ||
431 | { | ||
432 | concatStream.SetLength(maxArchiveSize); | ||
433 | } | ||
434 | |||
435 | Stream compressionStream = compressionStreamCreator(concatStream); | ||
436 | |||
437 | try | ||
438 | { | ||
439 | byte[] buf = new byte[4096]; | ||
440 | long bytesRemaining = fileStream.Length; | ||
441 | int counter = 0; | ||
442 | while (bytesRemaining > 0) | ||
443 | { | ||
444 | int count = (int) Math.Min( | ||
445 | bytesRemaining, (long) buf.Length); | ||
446 | |||
447 | count = fileCrcStream.Read(buf, 0, count); | ||
448 | if (count <= 0) | ||
449 | { | ||
450 | throw new ZipException( | ||
451 | "Failed to read file: " + this.currentFileName); | ||
452 | } | ||
453 | |||
454 | compressionStream.Write(buf, 0, count); | ||
455 | bytesRemaining -= count; | ||
456 | |||
457 | this.fileBytesProcessed += count; | ||
458 | this.currentFileBytesProcessed += count; | ||
459 | this.currentArchiveTotalBytes = concatStream.Source.Position; | ||
460 | this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes; | ||
461 | |||
462 | if (++counter % 16 == 0) // Report every 64K | ||
463 | { | ||
464 | this.OnProgress(ArchiveProgressType.PartialFile); | ||
465 | } | ||
466 | } | ||
467 | |||
468 | if (compressionStream is DeflateStream) | ||
469 | { | ||
470 | compressionStream.Close(); | ||
471 | } | ||
472 | else | ||
473 | { | ||
474 | compressionStream.Flush(); | ||
475 | } | ||
476 | } | ||
477 | finally | ||
478 | { | ||
479 | archiveStream = concatStream.Source; | ||
480 | } | ||
481 | |||
482 | bytesWritten += archiveStream.Position - writeStartPosition; | ||
483 | |||
484 | crc = fileCrcStream.Crc; | ||
485 | |||
486 | return bytesWritten; | ||
487 | } | ||
488 | } | ||
489 | } | ||