// FastZip.cs // // Copyright 2005 John Reilly // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // Linking this library statically or dynamically with other modules is // making a combined work based on this library. Thus, the terms and // conditions of the GNU General Public License cover the whole // combination. // // As a special exception, the copyright holders of this library give you // permission to link this library with independent modules to produce an // executable, regardless of the license terms of these independent // modules, and to copy and distribute the resulting executable under // terms of your choice, provided that you also meet, for each linked // independent module, the terms and conditions of the license of that // module. An independent module is a module which is not derived from // or based on this library. If you modify this library, you may extend // this exception to your version of the library, but you are not // obligated to do so. If you do not wish to do so, delete this // exception statement from your version. using System; using System.IO; using ICSharpCode.SharpZipLib.Core; namespace ICSharpCode.SharpZipLib.Zip { /// /// FastZipEvents supports all events applicable to FastZip operations. /// public class FastZipEvents { /// /// Delegate to invoke when processing directories. /// public ProcessDirectoryHandler ProcessDirectory; /// /// Delegate to invoke when processing files. /// public ProcessFileHandler ProcessFile; /// /// Delegate to invoke during processing of files. /// public ProgressHandler Progress; /// /// Delegate to invoke when processing for a file has been completed. /// public CompletedFileHandler CompletedFile; /// /// Delegate to invoke when processing directory failures. /// public DirectoryFailureHandler DirectoryFailure; /// /// Delegate to invoke when processing file failures. /// public FileFailureHandler FileFailure; /// /// Raise the directory failure event. /// /// The directory causing the failure. /// The exception for this event. /// A boolean indicating if execution should continue or not. public bool OnDirectoryFailure(string directory, Exception e) { bool result = false; DirectoryFailureHandler handler = DirectoryFailure; if ( handler != null ) { ScanFailureEventArgs args = new ScanFailureEventArgs(directory, e); handler(this, args); result = args.ContinueRunning; } return result; } /// /// Fires the file failure handler delegate. /// /// The file causing the failure. /// The exception for this failure. /// A boolean indicating if execution should continue or not. public bool OnFileFailure(string file, Exception e) { FileFailureHandler handler = FileFailure; bool result = (handler != null); if ( result ) { ScanFailureEventArgs args = new ScanFailureEventArgs(file, e); handler(this, args); result = args.ContinueRunning; } return result; } /// /// Fires the ProcessFile delegate. /// /// The file being processed. /// A boolean indicating if execution should continue or not. public bool OnProcessFile(string file) { bool result = true; ProcessFileHandler handler = ProcessFile; if ( handler != null ) { ScanEventArgs args = new ScanEventArgs(file); handler(this, args); result = args.ContinueRunning; } return result; } /// /// Fires the delegate /// /// The file whose processing has been completed. /// A boolean indicating if execution should continue or not. public bool OnCompletedFile(string file) { bool result = true; CompletedFileHandler handler = CompletedFile; if ( handler != null ) { ScanEventArgs args = new ScanEventArgs(file); handler(this, args); result = args.ContinueRunning; } return result; } /// /// Fires the process directory delegate. /// /// The directory being processed. /// Flag indicating if the directory has matching files as determined by the current filter. /// A of true if the operation should continue; false otherwise. public bool OnProcessDirectory(string directory, bool hasMatchingFiles) { bool result = true; ProcessDirectoryHandler handler = ProcessDirectory; if ( handler != null ) { DirectoryEventArgs args = new DirectoryEventArgs(directory, hasMatchingFiles); handler(this, args); result = args.ContinueRunning; } return result; } /// /// The minimum timespan between events. /// /// The minimum period of time between events. /// /// The default interval is three seconds. public TimeSpan ProgressInterval { get { return progressInterval_; } set { progressInterval_ = value; } } #region Instance Fields TimeSpan progressInterval_ = TimeSpan.FromSeconds(3); #endregion } /// /// FastZip provides facilities for creating and extracting zip files. /// public class FastZip { #region Enumerations /// /// Defines the desired handling when overwriting files during extraction. /// public enum Overwrite { /// /// Prompt the user to confirm overwriting /// Prompt, /// /// Never overwrite files. /// Never, /// /// Always overwrite files. /// Always } #endregion #region Constructors /// /// Initialise a default instance of . /// public FastZip() { } /// /// Initialise a new instance of /// /// The events to use during operations. public FastZip(FastZipEvents events) { events_ = events; } #endregion #region Properties /// /// Get/set a value indicating wether empty directories should be created. /// public bool CreateEmptyDirectories { get { return createEmptyDirectories_; } set { createEmptyDirectories_ = value; } } #if !NETCF_1_0 /// /// Get / set the password value. /// public string Password { get { return password_; } set { password_ = value; } } #endif /// /// Get or set the active when creating Zip files. /// /// public INameTransform NameTransform { get { return entryFactory_.NameTransform; } set { entryFactory_.NameTransform = value; } } /// /// Get or set the active when creating Zip files. /// public IEntryFactory EntryFactory { get { return entryFactory_; } set { if ( value == null ) { entryFactory_ = new ZipEntryFactory(); } else { entryFactory_ = value; } } } /// /// Gets or sets the setting for Zip64 handling when writing. /// /// /// The default value is dynamic which is not backwards compatible with old /// programs and can cause problems with XP's built in compression which cant /// read Zip64 archives. However it does avoid the situation were a large file /// is added and cannot be completed correctly. /// NOTE: Setting the size for entries before they are added is the best solution! /// By default the EntryFactory used by FastZip will set fhe file size. /// public UseZip64 UseZip64 { get { return useZip64_; } set { useZip64_ = value; } } /// /// Get/set a value indicating wether file dates and times should /// be restored when extracting files from an archive. /// /// The default value is false. public bool RestoreDateTimeOnExtract { get { return restoreDateTimeOnExtract_; } set { restoreDateTimeOnExtract_ = value; } } /// /// Get/set a value indicating wether file attributes should /// be restored during extract operations /// public bool RestoreAttributesOnExtract { get { return restoreAttributesOnExtract_; } set { restoreAttributesOnExtract_ = value; } } #endregion #region Delegates /// /// Delegate called when confirming overwriting of files. /// public delegate bool ConfirmOverwriteDelegate(string fileName); #endregion #region CreateZip /// /// Create a zip file. /// /// The name of the zip file to create. /// The directory to source files from. /// True to recurse directories, false for no recursion. /// The file filter to apply. /// The directory filter to apply. public void CreateZip(string zipFileName, string sourceDirectory, bool recurse, string fileFilter, string directoryFilter) { CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, directoryFilter); } /// /// Create a zip file/archive. /// /// The name of the zip file to create. /// The directory to obtain files and directories from. /// True to recurse directories, false for no recursion. /// The file filter to apply. public void CreateZip(string zipFileName, string sourceDirectory, bool recurse, string fileFilter) { CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, null); } /// /// Create a zip archive sending output to the passed. /// /// The stream to write archive data to. /// The directory to source files from. /// True to recurse directories, false for no recursion. /// The file filter to apply. /// The directory filter to apply. /// The is closed after creation. public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, string fileFilter, string directoryFilter) { NameTransform = new ZipNameTransform(sourceDirectory); sourceDirectory_ = sourceDirectory; using ( outputStream_ = new ZipOutputStream(outputStream) ) { #if !NETCF_1_0 if ( password_ != null ) { outputStream_.Password = password_; } #endif outputStream_.UseZip64 = UseZip64; FileSystemScanner scanner = new FileSystemScanner(fileFilter, directoryFilter); scanner.ProcessFile += new ProcessFileHandler(ProcessFile); if ( this.CreateEmptyDirectories ) { scanner.ProcessDirectory += new ProcessDirectoryHandler(ProcessDirectory); } if (events_ != null) { if ( events_.FileFailure != null ) { scanner.FileFailure += events_.FileFailure; } if ( events_.DirectoryFailure != null ) { scanner.DirectoryFailure += events_.DirectoryFailure; } } scanner.Scan(sourceDirectory, recurse); } } #endregion #region ExtractZip /// /// Extract the contents of a zip file. /// /// The zip file to extract from. /// The directory to save extracted information in. /// A filter to apply to files. public void ExtractZip(string zipFileName, string targetDirectory, string fileFilter) { ExtractZip(zipFileName, targetDirectory, Overwrite.Always, null, fileFilter, null, restoreDateTimeOnExtract_); } /// /// Extract the contents of a zip file. /// /// The zip file to extract from. /// The directory to save extracted information in. /// The style of overwriting to apply. /// A delegate to invoke when confirming overwriting. /// A filter to apply to files. /// A filter to apply to directories. /// Flag indicating whether to restore the date and time for extracted files. public void ExtractZip(string zipFileName, string targetDirectory, Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, string fileFilter, string directoryFilter, bool restoreDateTime) { Stream inputStream = File.Open(zipFileName, FileMode.Open, FileAccess.Read, FileShare.Read); ExtractZip(inputStream, targetDirectory, overwrite, confirmDelegate, fileFilter, directoryFilter, restoreDateTime, true); } /// /// Extract the contents of a zip file held in a stream. /// /// The seekable input stream containing the zip to extract from. /// The directory to save extracted information in. /// The style of overwriting to apply. /// A delegate to invoke when confirming overwriting. /// A filter to apply to files. /// A filter to apply to directories. /// Flag indicating whether to restore the date and time for extracted files. /// Flag indicating whether the inputStream will be closed by this method. public void ExtractZip(Stream inputStream, string targetDirectory, Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, string fileFilter, string directoryFilter, bool restoreDateTime, bool isStreamOwner) { if ((overwrite == Overwrite.Prompt) && (confirmDelegate == null)) { throw new ArgumentNullException("confirmDelegate"); } continueRunning_ = true; overwrite_ = overwrite; confirmDelegate_ = confirmDelegate; extractNameTransform_ = new WindowsNameTransform(targetDirectory); fileFilter_ = new NameFilter(fileFilter); directoryFilter_ = new NameFilter(directoryFilter); restoreDateTimeOnExtract_ = restoreDateTime; using (zipFile_ = new ZipFile(inputStream)) { #if !NETCF_1_0 if (password_ != null) { zipFile_.Password = password_; } #endif zipFile_.IsStreamOwner = isStreamOwner; System.Collections.IEnumerator enumerator = zipFile_.GetEnumerator(); while (continueRunning_ && enumerator.MoveNext()) { ZipEntry entry = (ZipEntry)enumerator.Current; if (entry.IsFile) { // TODO Path.GetDirectory can fail here on invalid characters. if (directoryFilter_.IsMatch(Path.GetDirectoryName(entry.Name)) && fileFilter_.IsMatch(entry.Name)) { ExtractEntry(entry); } } else if (entry.IsDirectory) { if (directoryFilter_.IsMatch(entry.Name) && CreateEmptyDirectories) { ExtractEntry(entry); } } else { // Do nothing for volume labels etc... } } } } #endregion #region Internal Processing void ProcessDirectory(object sender, DirectoryEventArgs e) { if ( !e.HasMatchingFiles && CreateEmptyDirectories ) { if ( events_ != null ) { events_.OnProcessDirectory(e.Name, e.HasMatchingFiles); } if ( e.ContinueRunning ) { if (e.Name != sourceDirectory_) { ZipEntry entry = entryFactory_.MakeDirectoryEntry(e.Name); outputStream_.PutNextEntry(entry); } } } } void ProcessFile(object sender, ScanEventArgs e) { if ( (events_ != null) && (events_.ProcessFile != null) ) { events_.ProcessFile(sender, e); } if ( e.ContinueRunning ) { try { // The open below is equivalent to OpenRead which gaurantees that if opened the // file will not be changed by subsequent openers, but precludes opening in some cases // were it could succeed. using (FileStream stream = File.Open(e.Name, FileMode.Open, FileAccess.Read, FileShare.Read)) { ZipEntry entry = entryFactory_.MakeFileEntry(e.Name); outputStream_.PutNextEntry(entry); AddFileContents(e.Name, stream); } } catch(Exception ex) { if (events_ != null) { continueRunning_ = events_.OnFileFailure(e.Name, ex); } else { continueRunning_ = false; throw; } } } } void AddFileContents(string name, Stream stream) { if( stream==null ) { throw new ArgumentNullException("stream"); } if( buffer_==null ) { buffer_=new byte[4096]; } if( (events_!=null)&&(events_.Progress!=null) ) { StreamUtils.Copy(stream, outputStream_, buffer_, events_.Progress, events_.ProgressInterval, this, name); } else { StreamUtils.Copy(stream, outputStream_, buffer_); } if( events_!=null ) { continueRunning_=events_.OnCompletedFile(name); } } void ExtractFileEntry(ZipEntry entry, string targetName) { bool proceed = true; if ( overwrite_ != Overwrite.Always ) { if ( File.Exists(targetName) ) { if ( (overwrite_ == Overwrite.Prompt) && (confirmDelegate_ != null) ) { proceed = confirmDelegate_(targetName); } else { proceed = false; } } } if ( proceed ) { if ( events_ != null ) { continueRunning_ = events_.OnProcessFile(entry.Name); } if ( continueRunning_ ) { try { using ( FileStream outputStream = File.Create(targetName) ) { if ( buffer_ == null ) { buffer_ = new byte[4096]; } if ((events_ != null) && (events_.Progress != null)) { StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_, events_.Progress, events_.ProgressInterval, this, entry.Name, entry.Size); } else { StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_); } if (events_ != null) { continueRunning_ = events_.OnCompletedFile(entry.Name); } } #if !NETCF_1_0 && !NETCF_2_0 if ( restoreDateTimeOnExtract_ ) { File.SetLastWriteTime(targetName, entry.DateTime); } if ( RestoreAttributesOnExtract && entry.IsDOSEntry && (entry.ExternalFileAttributes != -1)) { FileAttributes fileAttributes = (FileAttributes) entry.ExternalFileAttributes; // TODO: FastZip - Setting of other file attributes on extraction is a little trickier. fileAttributes &= (FileAttributes.Archive | FileAttributes.Normal | FileAttributes.ReadOnly | FileAttributes.Hidden); File.SetAttributes(targetName, fileAttributes); } #endif } catch(Exception ex) { if ( events_ != null ) { continueRunning_ = events_.OnFileFailure(targetName, ex); } else { continueRunning_ = false; throw; } } } } } void ExtractEntry(ZipEntry entry) { bool doExtraction = entry.IsCompressionMethodSupported(); string targetName = entry.Name; if ( doExtraction ) { if ( entry.IsFile ) { targetName = extractNameTransform_.TransformFile(targetName); } else if ( entry.IsDirectory ) { targetName = extractNameTransform_.TransformDirectory(targetName); } doExtraction = !((targetName == null) || (targetName.Length == 0)); } // TODO: Fire delegate/throw exception were compression method not supported, or name is invalid? string dirName = null; if ( doExtraction ) { if ( entry.IsDirectory ) { dirName = targetName; } else { dirName = Path.GetDirectoryName(Path.GetFullPath(targetName)); } } if ( doExtraction && !Directory.Exists(dirName) ) { if ( !entry.IsDirectory || CreateEmptyDirectories ) { try { Directory.CreateDirectory(dirName); } catch (Exception ex) { doExtraction = false; if ( events_ != null ) { if ( entry.IsDirectory ) { continueRunning_ = events_.OnDirectoryFailure(targetName, ex); } else { continueRunning_ = events_.OnFileFailure(targetName, ex); } } else { continueRunning_ = false; throw; } } } } if ( doExtraction && entry.IsFile ) { ExtractFileEntry(entry, targetName); } } static int MakeExternalAttributes(FileInfo info) { return (int)info.Attributes; } #if NET_1_0 || NET_1_1 || NETCF_1_0 static bool NameIsValid(string name) { return (name != null) && (name.Length > 0) && (name.IndexOfAny(Path.InvalidPathChars) < 0); } #else static bool NameIsValid(string name) { return (name != null) && (name.Length > 0) && (name.IndexOfAny(Path.GetInvalidPathChars()) < 0); } #endif #endregion #region Instance Fields bool continueRunning_; byte[] buffer_; ZipOutputStream outputStream_; ZipFile zipFile_; string sourceDirectory_; NameFilter fileFilter_; NameFilter directoryFilter_; Overwrite overwrite_; ConfirmOverwriteDelegate confirmDelegate_; bool restoreDateTimeOnExtract_; bool restoreAttributesOnExtract_; bool createEmptyDirectories_; FastZipEvents events_; IEntryFactory entryFactory_ = new ZipEntryFactory(); INameTransform extractNameTransform_; UseZip64 useZip64_=UseZip64.Dynamic; #if !NETCF_1_0 string password_; #endif #endregion } }