Added hammock project to debug streaming issues
[pithos-ms-client] / trunk / hammock / src / net35 / ICSharpCode.SharpZipLib.Silverlight / Zip / FastZip.cs
1 // FastZip.cs
2 //
3 // Copyright 2005 John Reilly
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 //
19 // Linking this library statically or dynamically with other modules is
20 // making a combined work based on this library.  Thus, the terms and
21 // conditions of the GNU General Public License cover the whole
22 // combination.
23 // 
24 // As a special exception, the copyright holders of this library give you
25 // permission to link this library with independent modules to produce an
26 // executable, regardless of the license terms of these independent
27 // modules, and to copy and distribute the resulting executable under
28 // terms of your choice, provided that you also meet, for each linked
29 // independent module, the terms and conditions of the license of that
30 // module.  An independent module is a module which is not derived from
31 // or based on this library.  If you modify this library, you may extend
32 // this exception to your version of the library, but you are not
33 // obligated to do so.  If you do not wish to do so, delete this
34 // exception statement from your version.
35
36 using System;
37 using System.IO;
38 using ICSharpCode.SharpZipLib.Silverlight.Core;
39 using ICSharpCode.SharpZipLib.Silverlight.Zip;
40 using ICSharpCode.SharpZipLib.Zip;
41
42 namespace ICSharpCode.SharpZipLib.Silverlight.Zip
43 {
44     /// <summary>
45     /// FastZipEvents supports all events applicable to <see cref="FastZip">FastZip</see> operations.
46     /// </summary>
47     public class FastZipEvents
48     {
49         /// <summary>
50         /// Delegate to invoke when processing directories.
51         /// </summary>
52         public ProcessDirectoryHandler ProcessDirectory;
53                 
54         /// <summary>
55         /// Delegate to invoke when processing files.
56         /// </summary>
57         public ProcessFileHandler ProcessFile;
58
59         /// <summary>
60         /// Delegate to invoke during processing of files.
61         /// </summary>
62         public ProgressHandler Progress;
63
64         /// <summary>
65         /// Delegate to invoke when processing for a file has been completed.
66         /// </summary>
67         public CompletedFileHandler CompletedFile;
68                 
69         /// <summary>
70         /// Delegate to invoke when processing directory failures.
71         /// </summary>
72         public DirectoryFailureHandler DirectoryFailure;
73                 
74         /// <summary>
75         /// Delegate to invoke when processing file failures.
76         /// </summary>
77         public FileFailureHandler FileFailure;
78                 
79         /// <summary>
80         /// Raise the <see cref="DirectoryFailure">directory failure</see> event.
81         /// </summary>
82         /// <param name="directory">The directory causing the failure.</param>
83         /// <param name="e">The exception for this event.</param>
84         /// <returns>A boolean indicating if execution should continue or not.</returns>
85         public bool OnDirectoryFailure(string directory, Exception e)
86         {
87             bool result = false;
88             if ( DirectoryFailure != null ) {
89                 var args = new ScanFailureEventArgs(directory, e);
90                 DirectoryFailure(this, args);
91                 result = args.ContinueRunning;
92             }
93             return result;
94         }
95                 
96         /// <summary>
97         /// Raises the <see cref="FileFailure">file failure delegate</see>.
98         /// </summary>
99         /// <param name="file">The file causing the failure.</param>
100         /// <param name="e">The exception for this failure.</param>
101         /// <returns>A boolean indicating if execution should continue or not.</returns>
102         public bool OnFileFailure(string file, Exception e)
103         {
104             bool result = false;
105             if ( FileFailure != null ) {
106                 var args = new ScanFailureEventArgs(file, e);
107                 FileFailure(this, args);
108                 result = args.ContinueRunning;
109             }
110             return result;
111         }
112                 
113         /// <summary>
114         /// Fires the <see cref="ProcessFile">Process File delegate</see>.
115         /// </summary>
116         /// <param name="file">The file being processed.</param>
117         /// <returns>A boolean indicating if execution should continue or not.</returns>
118         public bool OnProcessFile(string file)
119         {
120             bool result = true;
121             if ( ProcessFile != null ) {
122                 var args = new ScanEventArgs(file);
123                 ProcessFile(this, args);
124                 result = args.ContinueRunning;
125             }
126             return result;
127         }
128
129         /// <summary>
130         /// Fires the CompletedFile delegate
131         /// </summary>
132         /// <param name="file">The file whose processing has been completed.</param>
133         /// <returns>A boolean indicating if execution should continue or not.</returns>
134         public bool OnCompletedFile(string file)
135         {
136             var result = true;
137             if ( CompletedFile != null ) {
138                 var args = new ScanEventArgs(file);
139                 CompletedFile(this, args);
140                 result = args.ContinueRunning;
141             }
142             return result;
143         }
144                 
145         /// <summary>
146         /// Fires the <see cref="ProcessDirectory">process directory</see> delegate.
147         /// </summary>
148         /// <param name="directory">The directory being processed.</param>
149         /// <param name="hasMatchingFiles">Flag indicating if the directory has matching files as determined by the current filter.</param>
150         /// <returns>A <see cref="bool"/> of true if the operation should continue; false otherwise.</returns>
151         public bool OnProcessDirectory(string directory, bool hasMatchingFiles)
152         {
153             bool result = true;
154             if ( ProcessDirectory != null ) {
155                 var args = new DirectoryEventArgs(directory, hasMatchingFiles);
156                 ProcessDirectory(this, args);
157                 result = args.ContinueRunning;
158             }
159             return result;
160         }
161
162         /// <summary>
163         /// The minimum timespan between <see cref="Progress"/> events.
164         /// </summary>
165         /// <value>The minimum period of time between <see cref="Progress"/> events.</value>
166         /// <seealso cref="Progress"/>
167         public TimeSpan ProgressInterval
168         {
169             get { return progressInterval_; }
170             set { progressInterval_ = value; }
171         }
172
173         #region Instance Fields
174         TimeSpan progressInterval_ = TimeSpan.FromSeconds(3);
175         #endregion
176     }
177
178     /// <summary>
179     /// FastZip provides facilities for creating and extracting zip files.
180     /// </summary>
181     public class FastZip
182     {
183         #region Enumerations
184         /// <summary>
185         /// Defines the desired handling when overwriting files during extraction.
186         /// </summary>
187         public enum Overwrite 
188         {
189             /// <summary>
190             /// Prompt the user to confirm overwriting
191             /// </summary>
192             Prompt,
193             /// <summary>
194             /// Never overwrite files.
195             /// </summary>
196             Never,
197             /// <summary>
198             /// Always overwrite files.
199             /// </summary>
200             Always
201         }
202         #endregion
203                 
204         #region Constructors
205         /// <summary>
206         /// Initialise a default instance of <see cref="FastZip"/>.
207         /// </summary>
208         public FastZip()
209         {
210         }
211
212         /// <summary>
213         /// Initialise a new instance of <see cref="FastZip"/>
214         /// </summary>
215         /// <param name="events">The <see cref="FastZipEvents">events</see> to use during operations.</param>
216         public FastZip(FastZipEvents events)
217         {
218             events_ = events;
219         }
220         #endregion
221                 
222         #region Properties
223
224         /// <summary>
225         /// Get/set a value indicating wether empty directories should be created.
226         /// </summary>
227         public bool CreateEmptyDirectories { get; set; }
228
229         /// <summary>
230         /// Get / set the password value.
231         /// </summary>
232         public string Password
233         {
234             get { return password_; }
235             set { password_ = value; }
236         }
237
238         /// <summary>
239         /// Get or set the <see cref="INameTransform"></see> active when creating Zip files.
240         /// </summary>
241         /// <seealso cref="EntryFactory"></seealso>
242         public INameTransform NameTransform
243         {
244             get { return entryFactory_.NameTransform; }
245             set {
246                 entryFactory_.NameTransform = value;
247             }
248         }
249
250         /// <summary>
251         /// Get or set the <see cref="IEntryFactory"></see> active when creating Zip files.
252         /// </summary>
253         public IEntryFactory EntryFactory
254         {
255             get { return entryFactory_; }
256             set {
257                 entryFactory_ = value ?? new ZipEntryFactory();
258             }
259         }
260                 
261         /// <summary>
262         /// Get/set a value indicating wether file dates and times should 
263         /// be restored when extracting files from an archive.
264         /// </summary>
265         /// <remarks>The default value is false.</remarks>
266         public bool RestoreDateTimeOnExtract
267         {
268             get {
269                 return restoreDateTimeOnExtract_;
270             }
271             set {
272                 restoreDateTimeOnExtract_ = value;
273             }
274         }
275
276         /// <summary>
277         /// Get/set a value indicating wether file attributes should
278         /// be restored during extract operations
279         /// </summary>
280         public bool RestoreAttributesOnExtract { get; set; }
281
282         #endregion
283                 
284         #region Delegates
285         /// <summary>
286         /// Delegate called when confirming overwriting of files.
287         /// </summary>
288         public delegate bool ConfirmOverwriteDelegate(string fileName);
289         #endregion
290                 
291         #region CreateZip
292         /// <summary>
293         /// Create a zip file.
294         /// </summary>
295         /// <param name="zipFileName">The name of the zip file to create.</param>
296         /// <param name="sourceDirectory">The directory to source files from.</param>
297         /// <param name="recurse">True to recurse directories, false for no recursion.</param>
298         /// <param name="fileFilter">The <see cref="PathFilter">file filter</see> to apply.</param>
299         /// <param name="directoryFilter">The <see cref="PathFilter">directory filter</see> to apply.</param>
300         public void CreateZip(string zipFileName, string sourceDirectory, 
301                               bool recurse, string fileFilter, string directoryFilter)
302         {
303             CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, directoryFilter);
304         }
305                 
306         /// <summary>
307         /// Create a zip file/archive.
308         /// </summary>
309         /// <param name="zipFileName">The name of the zip file to create.</param>
310         /// <param name="sourceDirectory">The directory to obtain files and directories from.</param>
311         /// <param name="recurse">True to recurse directories, false for no recursion.</param>
312         /// <param name="fileFilter">The file filter to apply.</param>
313         public void CreateZip(string zipFileName, string sourceDirectory, bool recurse, string fileFilter)
314         {
315             CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, null);
316         }
317
318         /// <summary>
319         /// Create a zip archive sending output to the <paramref name="outputStream"/> passed.
320         /// </summary>
321         /// <param name="outputStream">The stream to write archive data to.</param>
322         /// <param name="sourceDirectory">The directory to source files from.</param>
323         /// <param name="recurse">True to recurse directories, false for no recursion.</param>
324         /// <param name="fileFilter">The <see cref="PathFilter">file filter</see> to apply.</param>
325         /// <param name="directoryFilter">The <see cref="PathFilter">directory filter</see> to apply.</param>
326         public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, string fileFilter, string directoryFilter)
327         {
328             NameTransform = new ZipNameTransform(sourceDirectory);
329             sourceDirectory_ = sourceDirectory;
330
331             using ( outputStream_ = new ZipOutputStream(outputStream) ) {
332
333                 if (password_ != null)
334                 {
335                     outputStream_.Password = password_;
336                 }
337
338                 var scanner = new FileSystemScanner(fileFilter, directoryFilter);
339                 scanner.ProcessFile += ProcessFile;
340                 if ( CreateEmptyDirectories ) {
341                     scanner.ProcessDirectory += ProcessDirectory;
342                 }
343                                 
344                 if (events_ != null) {
345                     if ( events_.FileFailure != null ) {
346                         scanner.FileFailure += events_.FileFailure;
347                     }
348
349                     if ( events_.DirectoryFailure != null ) {
350                         scanner.DirectoryFailure += events_.DirectoryFailure;
351                     }
352                 }
353
354                 scanner.Scan(sourceDirectory, recurse);
355             }
356         }
357
358         #endregion
359                 
360         #region ExtractZip
361         /// <summary>
362         /// Extract the contents of a zip file.
363         /// </summary>
364         /// <param name="zipFileName">The zip file to extract from.</param>
365         /// <param name="targetDirectory">The directory to save extracted information in.</param>
366         /// <param name="fileFilter">A filter to apply to files.</param>
367         public void ExtractZip(string zipFileName, string targetDirectory, string fileFilter) 
368         {
369             ExtractZip(zipFileName, targetDirectory, Overwrite.Always, null, fileFilter, null, restoreDateTimeOnExtract_);
370         }
371                 
372         /// <summary>
373         /// Extract the contents of a zip file.
374         /// </summary>
375         /// <param name="zipFileName">The zip file to extract from.</param>
376         /// <param name="targetDirectory">The directory to save extracted information in.</param>
377         /// <param name="overwrite">The style of <see cref="Overwrite">overwriting</see> to apply.</param>
378         /// <param name="confirmDelegate">A delegate to invoke when confirming overwriting.</param>
379         /// <param name="fileFilter">A filter to apply to files.</param>
380         /// <param name="directoryFilter">A filter to apply to directories.</param>
381         /// <param name="restoreDateTime">Flag indicating wether to restore the date and time for extracted files.</param>
382         public void ExtractZip(string zipFileName, string targetDirectory, 
383                                Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, 
384                                string fileFilter, string directoryFilter, bool restoreDateTime)
385         {
386             if ( (overwrite == Overwrite.Prompt) && (confirmDelegate == null) ) {
387                 throw new ArgumentNullException("confirmDelegate");
388             }
389
390             continueRunning_ = true;
391             overwrite_ = overwrite;
392             confirmDelegate_ = confirmDelegate;
393             targetDirectory_ = targetDirectory;
394             fileFilter_ = new NameFilter(fileFilter);
395             directoryFilter_ = new NameFilter(directoryFilter);
396             restoreDateTimeOnExtract_ = restoreDateTime;
397                         
398             using ( zipFile_ = new ZipFile(zipFileName) ) {
399
400 #if !NETCF_1_0
401                 if (password_ != null) {
402                     zipFile_.Password = password_;
403                 }
404 #endif
405
406                 System.Collections.IEnumerator enumerator = zipFile_.GetEnumerator();
407                 while ( continueRunning_ && enumerator.MoveNext()) {
408                     var entry = (ZipEntry) enumerator.Current;
409                     if ( entry.IsFile )
410                     {
411                         if ( directoryFilter_.IsMatch(Path.GetDirectoryName(entry.Name)) && fileFilter_.IsMatch(entry.Name) ) {
412                             ExtractEntry(entry);
413                         }
414                     }
415                     else if ( entry.IsDirectory ) {
416                         if ( directoryFilter_.IsMatch(entry.Name) && CreateEmptyDirectories ) {
417                             ExtractEntry(entry);
418                         }
419                     }
420                 }
421             }
422         }
423         #endregion
424                 
425         #region Internal Processing
426         void ProcessDirectory(object sender, DirectoryEventArgs e)
427         {
428             if ( !e.HasMatchingFiles && CreateEmptyDirectories ) {
429                 if ( events_ != null ) {
430                     events_.OnProcessDirectory(e.Name, e.HasMatchingFiles);
431                 }
432                                 
433                 if ( e.ContinueRunning ) {
434                     if (e.Name != sourceDirectory_) {
435                         ZipEntry entry = entryFactory_.MakeDirectoryEntry(e.Name);
436                         outputStream_.PutNextEntry(entry);
437                     }
438                 }
439             }
440         }
441                 
442         void ProcessFile(object sender, ScanEventArgs e)
443         {
444             if ( (events_ != null) && (events_.ProcessFile != null) ) {
445                 events_.ProcessFile(sender, e);
446             }
447                         
448             if ( e.ContinueRunning ) {
449                 ZipEntry entry = entryFactory_.MakeFileEntry(e.Name);
450                 outputStream_.PutNextEntry(entry);
451                 AddFileContents(e.Name);
452             }
453         }
454
455         void AddFileContents(string name)
456         {
457             if ( buffer_ == null ) {
458                 buffer_ = new byte[4096];
459             }
460
461             using (FileStream stream = File.OpenRead(name)) {
462                 if ((events_ != null) && (events_.Progress != null)) {
463                     StreamUtils.Copy(stream, outputStream_, buffer_,
464                                      events_.Progress, events_.ProgressInterval, this, name);
465                 }
466                 else {
467                     StreamUtils.Copy(stream, outputStream_, buffer_);
468                 }
469             }
470
471             if (events_ != null) {
472                 continueRunning_ = events_.OnCompletedFile(name);
473             }
474         }
475                 
476         void ExtractFileEntry(ZipEntry entry, string targetName)
477         {
478             bool proceed = true;
479             if ( overwrite_ != Overwrite.Always ) {
480                 if ( File.Exists(targetName) ) {
481                     if ( (overwrite_ == Overwrite.Prompt) && (confirmDelegate_ != null) ) {
482                         proceed = confirmDelegate_(targetName);
483                     }
484                     else {
485                         proceed = false;
486                     }
487                 }
488             }
489                         
490             if ( proceed ) {
491                 if ( events_ != null ) {
492                     continueRunning_ = events_.OnProcessFile(entry.Name);
493                 }
494                         
495                 if ( continueRunning_ ) {
496                     try {
497                         using ( FileStream outputStream = File.Create(targetName) ) {
498                             if ( buffer_ == null ) {
499                                 buffer_ = new byte[4096];
500                             }
501                             if ((events_ != null) && (events_.Progress != null))
502                             {
503                                 StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_,
504                                                  events_.Progress, events_.ProgressInterval, this, entry.Name);
505                             }
506                             else
507                             {
508                                 StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_);
509                             }
510                                                         
511                             if (events_ != null) {
512                                 continueRunning_ = events_.OnCompletedFile(entry.Name);
513                             }
514                         }
515
516                         //if ( restoreDateTimeOnExtract_ ) {
517                         //File.SetLastWriteTime(targetName, entry.DateTime);
518                         //}
519                                                 
520                         if ( RestoreAttributesOnExtract && entry.IsDOSEntry && (entry.ExternalFileAttributes != -1)) {
521                             var fileAttributes = (FileAttributes) entry.ExternalFileAttributes;
522                             // TODO: FastZip - Setting of other file attributes on extraction is a little trickier.
523                             fileAttributes &= (FileAttributes.Archive | FileAttributes.Normal | FileAttributes.ReadOnly | FileAttributes.Hidden);
524                             File.SetAttributes(targetName, fileAttributes);
525                         }
526                     }
527                     catch(Exception ex) {
528                         continueRunning_ = events_ != null && events_.OnFileFailure(targetName, ex);
529                     }
530                 }
531             }
532         }
533
534         void ExtractEntry(ZipEntry entry)
535         {
536             bool doExtraction = false;
537                         
538             string nameText = entry.Name;
539                         
540             if ( entry.IsFile ) {
541                 // TODO: Translate invalid names allowing extraction still.
542                 doExtraction = NameIsValid(nameText) && entry.IsCompressionMethodSupported();
543             }
544             else if ( entry.IsDirectory ) {
545                 doExtraction = NameIsValid(nameText);
546             }
547                         
548             // TODO: Fire delegate were compression method not supported, or name is invalid?
549
550             string dirName = null;
551             string targetName = null;
552                         
553             if ( doExtraction ) {
554                 // Handle invalid entry names by chopping of path root.
555                 if (Path.IsPathRooted(nameText)) {
556                     string workName = Path.GetPathRoot(nameText);
557                     nameText = nameText.Substring(workName.Length);
558                 }
559                                 
560                 if ( nameText.Length > 0 )
561                 {
562                     targetName = Path.Combine(targetDirectory_, nameText);
563                     dirName = entry.IsDirectory ? targetName : Path.GetDirectoryName(Path.GetFullPath(targetName));
564                 }
565                 else {
566                     doExtraction = false;
567                 }
568             }
569                         
570             if ( doExtraction && !Directory.Exists(dirName) ) {
571                 if ( !entry.IsDirectory || CreateEmptyDirectories ) {
572                     try {
573                         Directory.CreateDirectory(dirName);
574                     }
575                     catch (Exception ex) {
576                         doExtraction = false;
577                         if ( events_ != null ) {
578                             continueRunning_ = entry.IsDirectory ? events_.OnDirectoryFailure(targetName, ex) : events_.OnFileFailure(targetName, ex);
579                         }
580                         else {
581                             continueRunning_ = false;
582                         }
583                     }
584                 }
585             }
586                         
587             if ( doExtraction && entry.IsFile ) {
588                 ExtractFileEntry(entry, targetName);
589             }
590         }
591
592         static int MakeExternalAttributes(FileSystemInfo info)
593         {
594             return (int)info.Attributes;
595         }
596
597         static bool NameIsValid(string name)
598         {
599             return !string.IsNullOrEmpty(name) &&
600                    (name.IndexOfAny(Path.GetInvalidPathChars()) < 0);
601         }
602         #endregion
603                 
604         #region Instance Fields
605         bool continueRunning_;
606         byte[] buffer_;
607         ZipOutputStream outputStream_;
608         ZipFile zipFile_;
609         string targetDirectory_;
610         string sourceDirectory_;
611         NameFilter fileFilter_;
612         NameFilter directoryFilter_;
613         Overwrite overwrite_;
614         ConfirmOverwriteDelegate confirmDelegate_;
615                 
616         bool restoreDateTimeOnExtract_;
617         readonly FastZipEvents events_;
618         IEntryFactory entryFactory_ = new ZipEntryFactory();
619                 
620         string password_;
621
622         #endregion
623     }
624 }