3 // Copyright 2005 John Reilly
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.
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.
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.
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
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.
38 using ICSharpCode.SharpZipLib.Silverlight.Core;
39 using ICSharpCode.SharpZipLib.Silverlight.Zip;
40 using ICSharpCode.SharpZipLib.Zip;
42 namespace ICSharpCode.SharpZipLib.Silverlight.Zip
45 /// FastZipEvents supports all events applicable to <see cref="FastZip">FastZip</see> operations.
47 public class FastZipEvents
50 /// Delegate to invoke when processing directories.
52 public ProcessDirectoryHandler ProcessDirectory;
55 /// Delegate to invoke when processing files.
57 public ProcessFileHandler ProcessFile;
60 /// Delegate to invoke during processing of files.
62 public ProgressHandler Progress;
65 /// Delegate to invoke when processing for a file has been completed.
67 public CompletedFileHandler CompletedFile;
70 /// Delegate to invoke when processing directory failures.
72 public DirectoryFailureHandler DirectoryFailure;
75 /// Delegate to invoke when processing file failures.
77 public FileFailureHandler FileFailure;
80 /// Raise the <see cref="DirectoryFailure">directory failure</see> event.
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)
88 if ( DirectoryFailure != null ) {
89 var args = new ScanFailureEventArgs(directory, e);
90 DirectoryFailure(this, args);
91 result = args.ContinueRunning;
97 /// Raises the <see cref="FileFailure">file failure delegate</see>.
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)
105 if ( FileFailure != null ) {
106 var args = new ScanFailureEventArgs(file, e);
107 FileFailure(this, args);
108 result = args.ContinueRunning;
114 /// Fires the <see cref="ProcessFile">Process File delegate</see>.
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)
121 if ( ProcessFile != null ) {
122 var args = new ScanEventArgs(file);
123 ProcessFile(this, args);
124 result = args.ContinueRunning;
130 /// Fires the CompletedFile delegate
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)
137 if ( CompletedFile != null ) {
138 var args = new ScanEventArgs(file);
139 CompletedFile(this, args);
140 result = args.ContinueRunning;
146 /// Fires the <see cref="ProcessDirectory">process directory</see> delegate.
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)
154 if ( ProcessDirectory != null ) {
155 var args = new DirectoryEventArgs(directory, hasMatchingFiles);
156 ProcessDirectory(this, args);
157 result = args.ContinueRunning;
163 /// The minimum timespan between <see cref="Progress"/> events.
165 /// <value>The minimum period of time between <see cref="Progress"/> events.</value>
166 /// <seealso cref="Progress"/>
167 public TimeSpan ProgressInterval
169 get { return progressInterval_; }
170 set { progressInterval_ = value; }
173 #region Instance Fields
174 TimeSpan progressInterval_ = TimeSpan.FromSeconds(3);
179 /// FastZip provides facilities for creating and extracting zip files.
185 /// Defines the desired handling when overwriting files during extraction.
187 public enum Overwrite
190 /// Prompt the user to confirm overwriting
194 /// Never overwrite files.
198 /// Always overwrite files.
206 /// Initialise a default instance of <see cref="FastZip"/>.
213 /// Initialise a new instance of <see cref="FastZip"/>
215 /// <param name="events">The <see cref="FastZipEvents">events</see> to use during operations.</param>
216 public FastZip(FastZipEvents events)
225 /// Get/set a value indicating wether empty directories should be created.
227 public bool CreateEmptyDirectories { get; set; }
230 /// Get / set the password value.
232 public string Password
234 get { return password_; }
235 set { password_ = value; }
239 /// Get or set the <see cref="INameTransform"></see> active when creating Zip files.
241 /// <seealso cref="EntryFactory"></seealso>
242 public INameTransform NameTransform
244 get { return entryFactory_.NameTransform; }
246 entryFactory_.NameTransform = value;
251 /// Get or set the <see cref="IEntryFactory"></see> active when creating Zip files.
253 public IEntryFactory EntryFactory
255 get { return entryFactory_; }
257 entryFactory_ = value ?? new ZipEntryFactory();
262 /// Get/set a value indicating wether file dates and times should
263 /// be restored when extracting files from an archive.
265 /// <remarks>The default value is false.</remarks>
266 public bool RestoreDateTimeOnExtract
269 return restoreDateTimeOnExtract_;
272 restoreDateTimeOnExtract_ = value;
277 /// Get/set a value indicating wether file attributes should
278 /// be restored during extract operations
280 public bool RestoreAttributesOnExtract { get; set; }
286 /// Delegate called when confirming overwriting of files.
288 public delegate bool ConfirmOverwriteDelegate(string fileName);
293 /// Create a zip file.
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)
303 CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, directoryFilter);
307 /// Create a zip file/archive.
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)
315 CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, null);
319 /// Create a zip archive sending output to the <paramref name="outputStream"/> passed.
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)
328 NameTransform = new ZipNameTransform(sourceDirectory);
329 sourceDirectory_ = sourceDirectory;
331 using ( outputStream_ = new ZipOutputStream(outputStream) ) {
333 if (password_ != null)
335 outputStream_.Password = password_;
338 var scanner = new FileSystemScanner(fileFilter, directoryFilter);
339 scanner.ProcessFile += ProcessFile;
340 if ( CreateEmptyDirectories ) {
341 scanner.ProcessDirectory += ProcessDirectory;
344 if (events_ != null) {
345 if ( events_.FileFailure != null ) {
346 scanner.FileFailure += events_.FileFailure;
349 if ( events_.DirectoryFailure != null ) {
350 scanner.DirectoryFailure += events_.DirectoryFailure;
354 scanner.Scan(sourceDirectory, recurse);
362 /// Extract the contents of a zip file.
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)
369 ExtractZip(zipFileName, targetDirectory, Overwrite.Always, null, fileFilter, null, restoreDateTimeOnExtract_);
373 /// Extract the contents of a zip file.
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)
386 if ( (overwrite == Overwrite.Prompt) && (confirmDelegate == null) ) {
387 throw new ArgumentNullException("confirmDelegate");
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;
398 using ( zipFile_ = new ZipFile(zipFileName) ) {
401 if (password_ != null) {
402 zipFile_.Password = password_;
406 System.Collections.IEnumerator enumerator = zipFile_.GetEnumerator();
407 while ( continueRunning_ && enumerator.MoveNext()) {
408 var entry = (ZipEntry) enumerator.Current;
411 if ( directoryFilter_.IsMatch(Path.GetDirectoryName(entry.Name)) && fileFilter_.IsMatch(entry.Name) ) {
415 else if ( entry.IsDirectory ) {
416 if ( directoryFilter_.IsMatch(entry.Name) && CreateEmptyDirectories ) {
425 #region Internal Processing
426 void ProcessDirectory(object sender, DirectoryEventArgs e)
428 if ( !e.HasMatchingFiles && CreateEmptyDirectories ) {
429 if ( events_ != null ) {
430 events_.OnProcessDirectory(e.Name, e.HasMatchingFiles);
433 if ( e.ContinueRunning ) {
434 if (e.Name != sourceDirectory_) {
435 ZipEntry entry = entryFactory_.MakeDirectoryEntry(e.Name);
436 outputStream_.PutNextEntry(entry);
442 void ProcessFile(object sender, ScanEventArgs e)
444 if ( (events_ != null) && (events_.ProcessFile != null) ) {
445 events_.ProcessFile(sender, e);
448 if ( e.ContinueRunning ) {
449 ZipEntry entry = entryFactory_.MakeFileEntry(e.Name);
450 outputStream_.PutNextEntry(entry);
451 AddFileContents(e.Name);
455 void AddFileContents(string name)
457 if ( buffer_ == null ) {
458 buffer_ = new byte[4096];
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);
467 StreamUtils.Copy(stream, outputStream_, buffer_);
471 if (events_ != null) {
472 continueRunning_ = events_.OnCompletedFile(name);
476 void ExtractFileEntry(ZipEntry entry, string targetName)
479 if ( overwrite_ != Overwrite.Always ) {
480 if ( File.Exists(targetName) ) {
481 if ( (overwrite_ == Overwrite.Prompt) && (confirmDelegate_ != null) ) {
482 proceed = confirmDelegate_(targetName);
491 if ( events_ != null ) {
492 continueRunning_ = events_.OnProcessFile(entry.Name);
495 if ( continueRunning_ ) {
497 using ( FileStream outputStream = File.Create(targetName) ) {
498 if ( buffer_ == null ) {
499 buffer_ = new byte[4096];
501 if ((events_ != null) && (events_.Progress != null))
503 StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_,
504 events_.Progress, events_.ProgressInterval, this, entry.Name);
508 StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_);
511 if (events_ != null) {
512 continueRunning_ = events_.OnCompletedFile(entry.Name);
516 //if ( restoreDateTimeOnExtract_ ) {
517 //File.SetLastWriteTime(targetName, entry.DateTime);
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);
527 catch(Exception ex) {
528 continueRunning_ = events_ != null && events_.OnFileFailure(targetName, ex);
534 void ExtractEntry(ZipEntry entry)
536 bool doExtraction = false;
538 string nameText = entry.Name;
540 if ( entry.IsFile ) {
541 // TODO: Translate invalid names allowing extraction still.
542 doExtraction = NameIsValid(nameText) && entry.IsCompressionMethodSupported();
544 else if ( entry.IsDirectory ) {
545 doExtraction = NameIsValid(nameText);
548 // TODO: Fire delegate were compression method not supported, or name is invalid?
550 string dirName = null;
551 string targetName = null;
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);
560 if ( nameText.Length > 0 )
562 targetName = Path.Combine(targetDirectory_, nameText);
563 dirName = entry.IsDirectory ? targetName : Path.GetDirectoryName(Path.GetFullPath(targetName));
566 doExtraction = false;
570 if ( doExtraction && !Directory.Exists(dirName) ) {
571 if ( !entry.IsDirectory || CreateEmptyDirectories ) {
573 Directory.CreateDirectory(dirName);
575 catch (Exception ex) {
576 doExtraction = false;
577 if ( events_ != null ) {
578 continueRunning_ = entry.IsDirectory ? events_.OnDirectoryFailure(targetName, ex) : events_.OnFileFailure(targetName, ex);
581 continueRunning_ = false;
587 if ( doExtraction && entry.IsFile ) {
588 ExtractFileEntry(entry, targetName);
592 static int MakeExternalAttributes(FileSystemInfo info)
594 return (int)info.Attributes;
597 static bool NameIsValid(string name)
599 return !string.IsNullOrEmpty(name) &&
600 (name.IndexOfAny(Path.GetInvalidPathChars()) < 0);
604 #region Instance Fields
605 bool continueRunning_;
607 ZipOutputStream outputStream_;
609 string targetDirectory_;
610 string sourceDirectory_;
611 NameFilter fileFilter_;
612 NameFilter directoryFilter_;
613 Overwrite overwrite_;
614 ConfirmOverwriteDelegate confirmDelegate_;
616 bool restoreDateTimeOnExtract_;
617 readonly FastZipEvents events_;
618 IEntryFactory entryFactory_ = new ZipEntryFactory();