diff options
Diffstat (limited to '')
-rw-r--r-- | src/dtf/WixToolset.Dtf.Compression.Cab/CabUnpacker.cs | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabUnpacker.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabUnpacker.cs new file mode 100644 index 00000000..b0be4a15 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabUnpacker.cs | |||
@@ -0,0 +1,566 @@ | |||
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.Cab | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Text; | ||
8 | using System.Collections.Generic; | ||
9 | using System.Globalization; | ||
10 | using System.Runtime.InteropServices; | ||
11 | using System.Diagnostics.CodeAnalysis; | ||
12 | |||
13 | internal class CabUnpacker : CabWorker | ||
14 | { | ||
15 | private NativeMethods.FDI.Handle fdiHandle; | ||
16 | |||
17 | // These delegates need to be saved as member variables | ||
18 | // so that they don't get GC'd. | ||
19 | private NativeMethods.FDI.PFNALLOC fdiAllocMemHandler; | ||
20 | private NativeMethods.FDI.PFNFREE fdiFreeMemHandler; | ||
21 | private NativeMethods.FDI.PFNOPEN fdiOpenStreamHandler; | ||
22 | private NativeMethods.FDI.PFNREAD fdiReadStreamHandler; | ||
23 | private NativeMethods.FDI.PFNWRITE fdiWriteStreamHandler; | ||
24 | private NativeMethods.FDI.PFNCLOSE fdiCloseStreamHandler; | ||
25 | private NativeMethods.FDI.PFNSEEK fdiSeekStreamHandler; | ||
26 | |||
27 | private IUnpackStreamContext context; | ||
28 | |||
29 | private List<ArchiveFileInfo> fileList; | ||
30 | |||
31 | private int folderId; | ||
32 | |||
33 | private Predicate<string> filter; | ||
34 | |||
35 | public CabUnpacker(CabEngine cabEngine) | ||
36 | : base(cabEngine) | ||
37 | { | ||
38 | this.fdiAllocMemHandler = this.CabAllocMem; | ||
39 | this.fdiFreeMemHandler = this.CabFreeMem; | ||
40 | this.fdiOpenStreamHandler = this.CabOpenStream; | ||
41 | this.fdiReadStreamHandler = this.CabReadStream; | ||
42 | this.fdiWriteStreamHandler = this.CabWriteStream; | ||
43 | this.fdiCloseStreamHandler = this.CabCloseStream; | ||
44 | this.fdiSeekStreamHandler = this.CabSeekStream; | ||
45 | |||
46 | this.fdiHandle = NativeMethods.FDI.Create( | ||
47 | this.fdiAllocMemHandler, | ||
48 | this.fdiFreeMemHandler, | ||
49 | this.fdiOpenStreamHandler, | ||
50 | this.fdiReadStreamHandler, | ||
51 | this.fdiWriteStreamHandler, | ||
52 | this.fdiCloseStreamHandler, | ||
53 | this.fdiSeekStreamHandler, | ||
54 | NativeMethods.FDI.CPU_80386, | ||
55 | this.ErfHandle.AddrOfPinnedObject()); | ||
56 | if (this.Erf.Error) | ||
57 | { | ||
58 | int error = this.Erf.Oper; | ||
59 | int errorCode = this.Erf.Type; | ||
60 | this.ErfHandle.Free(); | ||
61 | throw new CabException( | ||
62 | error, | ||
63 | errorCode, | ||
64 | CabException.GetErrorMessage(error, errorCode, true)); | ||
65 | } | ||
66 | } | ||
67 | |||
68 | public bool IsArchive(Stream stream) | ||
69 | { | ||
70 | if (stream == null) | ||
71 | { | ||
72 | throw new ArgumentNullException("stream"); | ||
73 | } | ||
74 | |||
75 | lock (this) | ||
76 | { | ||
77 | short id; | ||
78 | int folderCount, fileCount; | ||
79 | return this.IsCabinet(stream, out id, out folderCount, out fileCount); | ||
80 | } | ||
81 | } | ||
82 | |||
83 | public IList<ArchiveFileInfo> GetFileInfo( | ||
84 | IUnpackStreamContext streamContext, | ||
85 | Predicate<string> fileFilter) | ||
86 | { | ||
87 | if (streamContext == null) | ||
88 | { | ||
89 | throw new ArgumentNullException("streamContext"); | ||
90 | } | ||
91 | |||
92 | lock (this) | ||
93 | { | ||
94 | this.context = streamContext; | ||
95 | this.filter = fileFilter; | ||
96 | this.NextCabinetName = String.Empty; | ||
97 | this.fileList = new List<ArchiveFileInfo>(); | ||
98 | bool tmpSuppress = this.SuppressProgressEvents; | ||
99 | this.SuppressProgressEvents = true; | ||
100 | try | ||
101 | { | ||
102 | for (short cabNumber = 0; | ||
103 | this.NextCabinetName != null; | ||
104 | cabNumber++) | ||
105 | { | ||
106 | this.Erf.Clear(); | ||
107 | this.CabNumbers[this.NextCabinetName] = cabNumber; | ||
108 | |||
109 | NativeMethods.FDI.Copy( | ||
110 | this.fdiHandle, | ||
111 | this.NextCabinetName, | ||
112 | String.Empty, | ||
113 | 0, | ||
114 | this.CabListNotify, | ||
115 | IntPtr.Zero, | ||
116 | IntPtr.Zero); | ||
117 | this.CheckError(true); | ||
118 | } | ||
119 | |||
120 | List<ArchiveFileInfo> tmpFileList = this.fileList; | ||
121 | this.fileList = null; | ||
122 | return tmpFileList.AsReadOnly(); | ||
123 | } | ||
124 | finally | ||
125 | { | ||
126 | this.SuppressProgressEvents = tmpSuppress; | ||
127 | |||
128 | if (this.CabStream != null) | ||
129 | { | ||
130 | this.context.CloseArchiveReadStream( | ||
131 | this.currentArchiveNumber, | ||
132 | this.currentArchiveName, | ||
133 | this.CabStream); | ||
134 | this.CabStream = null; | ||
135 | } | ||
136 | |||
137 | this.context = null; | ||
138 | } | ||
139 | } | ||
140 | } | ||
141 | |||
142 | public void Unpack( | ||
143 | IUnpackStreamContext streamContext, | ||
144 | Predicate<string> fileFilter) | ||
145 | { | ||
146 | lock (this) | ||
147 | { | ||
148 | IList<ArchiveFileInfo> files = | ||
149 | this.GetFileInfo(streamContext, fileFilter); | ||
150 | |||
151 | this.ResetProgressData(); | ||
152 | |||
153 | if (files != null) | ||
154 | { | ||
155 | this.totalFiles = files.Count; | ||
156 | |||
157 | for (int i = 0; i < files.Count; i++) | ||
158 | { | ||
159 | this.totalFileBytes += files[i].Length; | ||
160 | if (files[i].ArchiveNumber >= this.totalArchives) | ||
161 | { | ||
162 | int totalArchives = files[i].ArchiveNumber + 1; | ||
163 | this.totalArchives = (short) totalArchives; | ||
164 | } | ||
165 | } | ||
166 | } | ||
167 | |||
168 | this.context = streamContext; | ||
169 | this.fileList = null; | ||
170 | this.NextCabinetName = String.Empty; | ||
171 | this.folderId = -1; | ||
172 | this.currentFileNumber = -1; | ||
173 | |||
174 | try | ||
175 | { | ||
176 | for (short cabNumber = 0; | ||
177 | this.NextCabinetName != null; | ||
178 | cabNumber++) | ||
179 | { | ||
180 | this.Erf.Clear(); | ||
181 | this.CabNumbers[this.NextCabinetName] = cabNumber; | ||
182 | |||
183 | NativeMethods.FDI.Copy( | ||
184 | this.fdiHandle, | ||
185 | this.NextCabinetName, | ||
186 | String.Empty, | ||
187 | 0, | ||
188 | this.CabExtractNotify, | ||
189 | IntPtr.Zero, | ||
190 | IntPtr.Zero); | ||
191 | this.CheckError(true); | ||
192 | } | ||
193 | } | ||
194 | finally | ||
195 | { | ||
196 | if (this.CabStream != null) | ||
197 | { | ||
198 | this.context.CloseArchiveReadStream( | ||
199 | this.currentArchiveNumber, | ||
200 | this.currentArchiveName, | ||
201 | this.CabStream); | ||
202 | this.CabStream = null; | ||
203 | } | ||
204 | |||
205 | if (this.FileStream != null) | ||
206 | { | ||
207 | this.context.CloseFileWriteStream(this.currentFileName, this.FileStream, FileAttributes.Normal, DateTime.Now); | ||
208 | this.FileStream = null; | ||
209 | } | ||
210 | |||
211 | this.context = null; | ||
212 | } | ||
213 | } | ||
214 | } | ||
215 | |||
216 | internal override int CabOpenStreamEx(string path, int openFlags, int shareMode, out int err, IntPtr pv) | ||
217 | { | ||
218 | if (this.CabNumbers.ContainsKey(path)) | ||
219 | { | ||
220 | Stream stream = this.CabStream; | ||
221 | if (stream == null) | ||
222 | { | ||
223 | short cabNumber = this.CabNumbers[path]; | ||
224 | |||
225 | stream = this.context.OpenArchiveReadStream(cabNumber, path, this.CabEngine); | ||
226 | if (stream == null) | ||
227 | { | ||
228 | throw new FileNotFoundException(String.Format(CultureInfo.InvariantCulture, "Cabinet {0} not provided.", cabNumber)); | ||
229 | } | ||
230 | this.currentArchiveName = path; | ||
231 | this.currentArchiveNumber = cabNumber; | ||
232 | if (this.totalArchives <= this.currentArchiveNumber) | ||
233 | { | ||
234 | int totalArchives = this.currentArchiveNumber + 1; | ||
235 | this.totalArchives = (short) totalArchives; | ||
236 | } | ||
237 | this.currentArchiveTotalBytes = stream.Length; | ||
238 | this.currentArchiveBytesProcessed = 0; | ||
239 | |||
240 | if (this.folderId != -3) // -3 is a special folderId that requires re-opening the same cab | ||
241 | { | ||
242 | this.OnProgress(ArchiveProgressType.StartArchive); | ||
243 | } | ||
244 | this.CabStream = stream; | ||
245 | } | ||
246 | path = CabWorker.CabStreamName; | ||
247 | } | ||
248 | return base.CabOpenStreamEx(path, openFlags, shareMode, out err, pv); | ||
249 | } | ||
250 | |||
251 | internal override int CabReadStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv) | ||
252 | { | ||
253 | int count = base.CabReadStreamEx(streamHandle, memory, cb, out err, pv); | ||
254 | if (err == 0 && this.CabStream != null) | ||
255 | { | ||
256 | if (this.fileList == null) | ||
257 | { | ||
258 | Stream stream = this.StreamHandles[streamHandle]; | ||
259 | if (DuplicateStream.OriginalStream(stream) == | ||
260 | DuplicateStream.OriginalStream(this.CabStream)) | ||
261 | { | ||
262 | this.currentArchiveBytesProcessed += cb; | ||
263 | if (this.currentArchiveBytesProcessed > this.currentArchiveTotalBytes) | ||
264 | { | ||
265 | this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes; | ||
266 | } | ||
267 | } | ||
268 | } | ||
269 | } | ||
270 | return count; | ||
271 | } | ||
272 | |||
273 | internal override int CabWriteStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv) | ||
274 | { | ||
275 | int count = base.CabWriteStreamEx(streamHandle, memory, cb, out err, pv); | ||
276 | if (count > 0 && err == 0) | ||
277 | { | ||
278 | this.currentFileBytesProcessed += cb; | ||
279 | this.fileBytesProcessed += cb; | ||
280 | this.OnProgress(ArchiveProgressType.PartialFile); | ||
281 | } | ||
282 | return count; | ||
283 | } | ||
284 | |||
285 | internal override int CabCloseStreamEx(int streamHandle, out int err, IntPtr pv) | ||
286 | { | ||
287 | Stream stream = DuplicateStream.OriginalStream(this.StreamHandles[streamHandle]); | ||
288 | |||
289 | if (stream == DuplicateStream.OriginalStream(this.CabStream)) | ||
290 | { | ||
291 | if (this.folderId != -3) // -3 is a special folderId that requires re-opening the same cab | ||
292 | { | ||
293 | this.OnProgress(ArchiveProgressType.FinishArchive); | ||
294 | } | ||
295 | |||
296 | this.context.CloseArchiveReadStream(this.currentArchiveNumber, this.currentArchiveName, stream); | ||
297 | |||
298 | this.currentArchiveName = this.NextCabinetName; | ||
299 | this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes = 0; | ||
300 | |||
301 | this.CabStream = null; | ||
302 | } | ||
303 | return base.CabCloseStreamEx(streamHandle, out err, pv); | ||
304 | } | ||
305 | |||
306 | /// <summary> | ||
307 | /// Disposes of resources allocated by the cabinet engine. | ||
308 | /// </summary> | ||
309 | /// <param name="disposing">If true, the method has been called directly or indirectly by a user's code, | ||
310 | /// so managed and unmanaged resources will be disposed. If false, the method has been called by the | ||
311 | /// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param> | ||
312 | [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")] | ||
313 | protected override void Dispose(bool disposing) | ||
314 | { | ||
315 | try | ||
316 | { | ||
317 | if (disposing) | ||
318 | { | ||
319 | if (this.fdiHandle != null) | ||
320 | { | ||
321 | this.fdiHandle.Dispose(); | ||
322 | this.fdiHandle = null; | ||
323 | } | ||
324 | } | ||
325 | } | ||
326 | finally | ||
327 | { | ||
328 | base.Dispose(disposing); | ||
329 | } | ||
330 | } | ||
331 | |||
332 | private static string GetFileName(NativeMethods.FDI.NOTIFICATION notification) | ||
333 | { | ||
334 | bool utf8Name = (notification.attribs & (ushort) FileAttributes.Normal) != 0; // _A_NAME_IS_UTF | ||
335 | |||
336 | // Non-utf8 names should be completely ASCII. But for compatibility with | ||
337 | // legacy tools, interpret them using the current (Default) ANSI codepage. | ||
338 | Encoding nameEncoding = utf8Name ? Encoding.UTF8 : Encoding.Default; | ||
339 | |||
340 | // Find how many bytes are in the string. | ||
341 | // Unfortunately there is no faster way. | ||
342 | int nameBytesCount = 0; | ||
343 | while (Marshal.ReadByte(notification.psz1, nameBytesCount) != 0) | ||
344 | { | ||
345 | nameBytesCount++; | ||
346 | } | ||
347 | |||
348 | byte[] nameBytes = new byte[nameBytesCount]; | ||
349 | Marshal.Copy(notification.psz1, nameBytes, 0, nameBytesCount); | ||
350 | string name = nameEncoding.GetString(nameBytes); | ||
351 | if (Path.IsPathRooted(name)) | ||
352 | { | ||
353 | name = name.Replace("" + Path.VolumeSeparatorChar, ""); | ||
354 | } | ||
355 | |||
356 | return name; | ||
357 | } | ||
358 | |||
359 | private bool IsCabinet(Stream cabStream, out short id, out int cabFolderCount, out int fileCount) | ||
360 | { | ||
361 | int streamHandle = this.StreamHandles.AllocHandle(cabStream); | ||
362 | try | ||
363 | { | ||
364 | this.Erf.Clear(); | ||
365 | NativeMethods.FDI.CABINFO fdici; | ||
366 | bool isCabinet = 0 != NativeMethods.FDI.IsCabinet(this.fdiHandle, streamHandle, out fdici); | ||
367 | |||
368 | if (this.Erf.Error) | ||
369 | { | ||
370 | if (((NativeMethods.FDI.ERROR) this.Erf.Oper) == NativeMethods.FDI.ERROR.UNKNOWN_CABINET_VERSION) | ||
371 | { | ||
372 | isCabinet = false; | ||
373 | } | ||
374 | else | ||
375 | { | ||
376 | throw new CabException( | ||
377 | this.Erf.Oper, | ||
378 | this.Erf.Type, | ||
379 | CabException.GetErrorMessage(this.Erf.Oper, this.Erf.Type, true)); | ||
380 | } | ||
381 | } | ||
382 | |||
383 | id = fdici.setID; | ||
384 | cabFolderCount = (int) fdici.cFolders; | ||
385 | fileCount = (int) fdici.cFiles; | ||
386 | return isCabinet; | ||
387 | } | ||
388 | finally | ||
389 | { | ||
390 | this.StreamHandles.FreeHandle(streamHandle); | ||
391 | } | ||
392 | } | ||
393 | |||
394 | private int CabListNotify(NativeMethods.FDI.NOTIFICATIONTYPE notificationType, NativeMethods.FDI.NOTIFICATION notification) | ||
395 | { | ||
396 | switch (notificationType) | ||
397 | { | ||
398 | case NativeMethods.FDI.NOTIFICATIONTYPE.CABINET_INFO: | ||
399 | { | ||
400 | string nextCab = Marshal.PtrToStringAnsi(notification.psz1); | ||
401 | this.NextCabinetName = (nextCab.Length != 0 ? nextCab : null); | ||
402 | return 0; // Continue | ||
403 | } | ||
404 | case NativeMethods.FDI.NOTIFICATIONTYPE.PARTIAL_FILE: | ||
405 | { | ||
406 | // This notification can occur when examining the contents of a non-first cab file. | ||
407 | return 0; // Continue | ||
408 | } | ||
409 | case NativeMethods.FDI.NOTIFICATIONTYPE.COPY_FILE: | ||
410 | { | ||
411 | //bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC | ||
412 | |||
413 | string name = CabUnpacker.GetFileName(notification); | ||
414 | |||
415 | if (this.filter == null || this.filter(name)) | ||
416 | { | ||
417 | if (this.fileList != null) | ||
418 | { | ||
419 | FileAttributes attributes = (FileAttributes) notification.attribs & | ||
420 | (FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System); | ||
421 | if (attributes == (FileAttributes) 0) | ||
422 | { | ||
423 | attributes = FileAttributes.Normal; | ||
424 | } | ||
425 | DateTime lastWriteTime; | ||
426 | CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime); | ||
427 | long length = notification.cb; | ||
428 | |||
429 | CabFileInfo fileInfo = new CabFileInfo( | ||
430 | name, | ||
431 | notification.iFolder, | ||
432 | notification.iCabinet, | ||
433 | attributes, | ||
434 | lastWriteTime, | ||
435 | length); | ||
436 | this.fileList.Add(fileInfo); | ||
437 | this.currentFileNumber = this.fileList.Count - 1; | ||
438 | this.fileBytesProcessed += notification.cb; | ||
439 | } | ||
440 | } | ||
441 | |||
442 | this.totalFiles++; | ||
443 | this.totalFileBytes += notification.cb; | ||
444 | return 0; // Continue | ||
445 | } | ||
446 | } | ||
447 | return 0; | ||
448 | } | ||
449 | |||
450 | private int CabExtractNotify(NativeMethods.FDI.NOTIFICATIONTYPE notificationType, NativeMethods.FDI.NOTIFICATION notification) | ||
451 | { | ||
452 | switch (notificationType) | ||
453 | { | ||
454 | case NativeMethods.FDI.NOTIFICATIONTYPE.CABINET_INFO: | ||
455 | { | ||
456 | if (this.NextCabinetName != null && this.NextCabinetName.StartsWith("?", StringComparison.Ordinal)) | ||
457 | { | ||
458 | // We are just continuing the copy of a file that spanned cabinets. | ||
459 | // The next cabinet name needs to be preserved. | ||
460 | this.NextCabinetName = this.NextCabinetName.Substring(1); | ||
461 | } | ||
462 | else | ||
463 | { | ||
464 | string nextCab = Marshal.PtrToStringAnsi(notification.psz1); | ||
465 | this.NextCabinetName = (nextCab.Length != 0 ? nextCab : null); | ||
466 | } | ||
467 | return 0; // Continue | ||
468 | } | ||
469 | case NativeMethods.FDI.NOTIFICATIONTYPE.NEXT_CABINET: | ||
470 | { | ||
471 | string nextCab = Marshal.PtrToStringAnsi(notification.psz1); | ||
472 | this.CabNumbers[nextCab] = (short) notification.iCabinet; | ||
473 | this.NextCabinetName = "?" + this.NextCabinetName; | ||
474 | return 0; // Continue | ||
475 | } | ||
476 | case NativeMethods.FDI.NOTIFICATIONTYPE.COPY_FILE: | ||
477 | { | ||
478 | return this.CabExtractCopyFile(notification); | ||
479 | } | ||
480 | case NativeMethods.FDI.NOTIFICATIONTYPE.CLOSE_FILE_INFO: | ||
481 | { | ||
482 | return this.CabExtractCloseFile(notification); | ||
483 | } | ||
484 | } | ||
485 | return 0; | ||
486 | } | ||
487 | |||
488 | private int CabExtractCopyFile(NativeMethods.FDI.NOTIFICATION notification) | ||
489 | { | ||
490 | if (notification.iFolder != this.folderId) | ||
491 | { | ||
492 | if (notification.iFolder != -3) // -3 is a special folderId used when continuing a folder from a previous cab | ||
493 | { | ||
494 | if (this.folderId != -1) // -1 means we just started the extraction sequence | ||
495 | { | ||
496 | this.currentFolderNumber++; | ||
497 | } | ||
498 | } | ||
499 | this.folderId = notification.iFolder; | ||
500 | } | ||
501 | |||
502 | //bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC | ||
503 | |||
504 | string name = CabUnpacker.GetFileName(notification); | ||
505 | |||
506 | if (this.filter == null || this.filter(name)) | ||
507 | { | ||
508 | this.currentFileNumber++; | ||
509 | this.currentFileName = name; | ||
510 | |||
511 | this.currentFileBytesProcessed = 0; | ||
512 | this.currentFileTotalBytes = notification.cb; | ||
513 | this.OnProgress(ArchiveProgressType.StartFile); | ||
514 | |||
515 | DateTime lastWriteTime; | ||
516 | CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime); | ||
517 | |||
518 | Stream stream = this.context.OpenFileWriteStream(name, notification.cb, lastWriteTime); | ||
519 | if (stream != null) | ||
520 | { | ||
521 | this.FileStream = stream; | ||
522 | int streamHandle = this.StreamHandles.AllocHandle(stream); | ||
523 | return streamHandle; | ||
524 | } | ||
525 | else | ||
526 | { | ||
527 | this.fileBytesProcessed += notification.cb; | ||
528 | this.OnProgress(ArchiveProgressType.FinishFile); | ||
529 | this.currentFileName = null; | ||
530 | } | ||
531 | } | ||
532 | return 0; // Continue | ||
533 | } | ||
534 | |||
535 | private int CabExtractCloseFile(NativeMethods.FDI.NOTIFICATION notification) | ||
536 | { | ||
537 | Stream stream = this.StreamHandles[notification.hf]; | ||
538 | this.StreamHandles.FreeHandle(notification.hf); | ||
539 | |||
540 | //bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC | ||
541 | |||
542 | string name = CabUnpacker.GetFileName(notification); | ||
543 | |||
544 | FileAttributes attributes = (FileAttributes) notification.attribs & | ||
545 | (FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System); | ||
546 | if (attributes == (FileAttributes) 0) | ||
547 | { | ||
548 | attributes = FileAttributes.Normal; | ||
549 | } | ||
550 | DateTime lastWriteTime; | ||
551 | CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime); | ||
552 | |||
553 | stream.Flush(); | ||
554 | this.context.CloseFileWriteStream(name, stream, attributes, lastWriteTime); | ||
555 | this.FileStream = null; | ||
556 | |||
557 | long remainder = this.currentFileTotalBytes - this.currentFileBytesProcessed; | ||
558 | this.currentFileBytesProcessed += remainder; | ||
559 | this.fileBytesProcessed += remainder; | ||
560 | this.OnProgress(ArchiveProgressType.FinishFile); | ||
561 | this.currentFileName = null; | ||
562 | |||
563 | return 1; // Continue | ||
564 | } | ||
565 | } | ||
566 | } | ||