Skip to content

feat(fastzip): allow custom filesystemscanner #678

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 76 additions & 185 deletions src/ICSharpCode.SharpZipLib/Core/FileSystemScanner.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace ICSharpCode.SharpZipLib.Core
{
@@ -9,51 +12,31 @@ namespace ICSharpCode.SharpZipLib.Core
/// </summary>
public class ScanEventArgs : EventArgs
{
#region Constructors

/// <summary>
/// Initialise a new instance of <see cref="ScanEventArgs"/>
/// </summary>
/// <param name="name">The file or directory name.</param>
public ScanEventArgs(string name)
{
name_ = name;
Name = name;
}

#endregion Constructors

/// <summary>
/// The file or directory name for this event.
/// </summary>
public string Name
{
get { return name_; }
}
public string Name { get; }

/// <summary>
/// Get set a value indicating if scanning should continue or not.
/// </summary>
public bool ContinueRunning
{
get { return continueRunning_; }
set { continueRunning_ = value; }
}

#region Instance Fields

private string name_;
private bool continueRunning_ = true;

#endregion Instance Fields
public bool ContinueRunning { get; set; } = true;
}

/// <summary>
/// Event arguments during processing of a single file or directory.
/// </summary>
public class ProgressEventArgs : EventArgs
{
#region Constructors

/// <summary>
/// Initialise a new instance of <see cref="ScanEventArgs"/>
/// </summary>
@@ -62,85 +45,44 @@ public class ProgressEventArgs : EventArgs
/// <param name="target">The total number of bytes to process, 0 if not known</param>
public ProgressEventArgs(string name, long processed, long target)
{
name_ = name;
processed_ = processed;
target_ = target;
Name = name;
Processed = processed;
Target = target;
}

#endregion Constructors

/// <summary>
/// The name for this event if known.
/// </summary>
public string Name
{
get { return name_; }
}
public string Name { get; }

/// <summary>
/// Get set a value indicating whether scanning should continue or not.
/// </summary>
public bool ContinueRunning
{
get { return continueRunning_; }
set { continueRunning_ = value; }
}
public bool ContinueRunning { get; set; } = true;

/// <summary>
/// Get a percentage representing how much of the <see cref="Target"></see> has been processed
/// </summary>
/// <value>0.0 to 100.0 percent; 0 if target is not known.</value>
public float PercentComplete
{
get
{
float result;
if (target_ <= 0)
{
result = 0;
}
else
{
result = ((float)processed_ / (float)target_) * 100.0f;
}
return result;
}
}
public float PercentComplete => Target <= 0 ? 0 : ((float)Processed / (float)Target) * 100.0f;

/// <summary>
/// The number of bytes processed so far
/// </summary>
public long Processed
{
get { return processed_; }
}
public long Processed { get; }

/// <summary>
/// The number of bytes to process.
/// </summary>
/// <remarks>Target may be 0 or negative if the value isnt known.</remarks>
public long Target
{
get { return target_; }
}

#region Instance Fields

private string name_;
private long processed_;
private long target_;
private bool continueRunning_ = true;

#endregion Instance Fields
public long Target { get; }
}

/// <summary>
/// Event arguments for directories.
/// </summary>
public class DirectoryEventArgs : ScanEventArgs
{
#region Constructors

/// <summary>
/// Initialize an instance of <see cref="DirectoryEventArgs"></see>.
/// </summary>
@@ -149,26 +91,13 @@ public class DirectoryEventArgs : ScanEventArgs
public DirectoryEventArgs(string name, bool hasMatchingFiles)
: base(name)
{
hasMatchingFiles_ = hasMatchingFiles;
HasMatchingFiles = hasMatchingFiles;
}

#endregion Constructors

/// <summary>
/// Get a value indicating if the directory contains any matching files or not.
/// </summary>
public bool HasMatchingFiles
{
get { return hasMatchingFiles_; }
}

private readonly

#region Instance Fields

bool hasMatchingFiles_;

#endregion Instance Fields
public bool HasMatchingFiles { get; }
}

/// <summary>
@@ -185,45 +114,26 @@ public class ScanFailureEventArgs : EventArgs
/// <param name="e">The exception to use.</param>
public ScanFailureEventArgs(string name, Exception e)
{
name_ = name;
exception_ = e;
continueRunning_ = true;
Name = name;
Exception = e;
}

#endregion Constructors

/// <summary>
/// The applicable name.
/// </summary>
public string Name
{
get { return name_; }
}
public string Name { get; }

/// <summary>
/// The applicable exception.
/// </summary>
public Exception Exception
{
get { return exception_; }
}
public Exception Exception { get; }

/// <summary>
/// Get / set a value indicating whether scanning should continue.
/// </summary>
public bool ContinueRunning
{
get { return continueRunning_; }
set { continueRunning_ = value; }
}

#region Instance Fields

private string name_;
private Exception exception_;
private bool continueRunning_;

#endregion Instance Fields
public bool ContinueRunning { get; set; } = true;
}

#endregion EventArgs
@@ -352,15 +262,12 @@ public FileSystemScanner(IScanFilter fileFilter, IScanFilter directoryFilter)
/// <param name="e">The exception detected.</param>
private bool OnDirectoryFailure(string directory, Exception e)
{
DirectoryFailureHandler handler = DirectoryFailure;
bool result = (handler != null);
if (result)
{
var args = new ScanFailureEventArgs(directory, e);
handler(this, args);
alive_ = args.ContinueRunning;
}
return result;
if (DirectoryFailure == null) return false;

var args = new ScanFailureEventArgs(directory, e);
DirectoryFailure(this, args);
alive_ = args.ContinueRunning;
return true;
}

/// <summary>
@@ -370,17 +277,12 @@ private bool OnDirectoryFailure(string directory, Exception e)
/// <param name="e">The exception detected.</param>
private bool OnFileFailure(string file, Exception e)
{
FileFailureHandler handler = FileFailure;

bool result = (handler != null);

if (result)
{
var args = new ScanFailureEventArgs(file, e);
FileFailure(this, args);
alive_ = args.ContinueRunning;
}
return result;
if (FileFailure == null) return false;

var args = new ScanFailureEventArgs(file, e);
FileFailure(this, args);
alive_ = args.ContinueRunning;
return true;
}

/// <summary>
@@ -389,14 +291,11 @@ private bool OnFileFailure(string file, Exception e)
/// <param name="file">The file name.</param>
private void OnProcessFile(string file)
{
ProcessFileHandler handler = ProcessFile;

if (handler != null)
{
var args = new ScanEventArgs(file);
handler(this, args);
alive_ = args.ContinueRunning;
}
if (ProcessFile == null) return;

var args = new ScanEventArgs(file);
ProcessFile(this, args);
alive_ = args.ContinueRunning;
}

/// <summary>
@@ -405,14 +304,10 @@ private void OnProcessFile(string file)
/// <param name="file">The file name</param>
private void OnCompleteFile(string file)
{
CompletedFileHandler handler = CompletedFile;

if (handler != null)
{
var args = new ScanEventArgs(file);
handler(this, args);
alive_ = args.ContinueRunning;
}
if (CompletedFile == null) return;
var args = new ScanEventArgs(file);
CompletedFile(this, args);
alive_ = args.ContinueRunning;
}

/// <summary>
@@ -443,39 +338,37 @@ public void Scan(string directory, bool recurse)
ScanDir(directory, recurse);
}

/// <summary>
/// The method used to enumerate files in the supplied <paramref name="directory"/>
/// </summary>
/// <param name="directory"></param>
protected virtual IEnumerable<string> GetFiles(string directory) => Directory.EnumerateFiles(directory);

/// <summary>
/// The method used to enumerate directories in the supplied <paramref name="directory"/>
/// </summary>
/// <param name="directory"></param>
protected virtual IEnumerable<string> GetDirectories(string directory) => Directory.EnumerateDirectories(directory);

private void ScanDir(string directory, bool recurse)
{
try
{
string[] names = System.IO.Directory.GetFiles(directory);
bool hasMatch = false;
for (int fileIndex = 0; fileIndex < names.Length; ++fileIndex)
{
if (!fileFilter_.IsMatch(names[fileIndex]))
{
names[fileIndex] = null;
}
else
{
hasMatch = true;
}
}

var names = GetFiles(directory).Where(fileFilter_.IsMatch).ToArray();
var hasMatch = names.Any();

OnProcessDirectory(directory, hasMatch);

if (alive_ && hasMatch)
{
foreach (string fileName in names)
foreach (var fileName in names)
{
try
{
if (fileName != null)
OnProcessFile(fileName);
if (!alive_)
{
OnProcessFile(fileName);
if (!alive_)
{
break;
}
break;
}
}
catch (Exception e)
@@ -496,31 +389,29 @@ private void ScanDir(string directory, bool recurse)
}
}

if (alive_ && recurse)
if (!alive_ || !recurse) return;

try
{
try
var subDirectories = GetDirectories(directory)
.Where(d => d != null && directoryFilter_.IsMatch(d));
Comment on lines +396 to +397
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check direcotryFilter_ for null. Perhaps by

Suggested change
var subDirectories = GetDirectories(directory)
.Where(d => d != null && directoryFilter_.IsMatch(d));
Func<bool, string> filter = directoryFilter_ == null
? (d) => true
: (d) => directoryFilter_.IsMatch(d);
var subDirectories = GetDirectories(directory)
.Where(d => d != null && filter(d));

foreach (var fulldir in subDirectories)
{
string[] names = System.IO.Directory.GetDirectories(directory);
foreach (string fulldir in names)
ScanDir(fulldir, recurse: true);
if (!alive_)
{
if ((directoryFilter_ == null) || (directoryFilter_.IsMatch(fulldir)))
{
ScanDir(fulldir, true);
if (!alive_)
{
break;
}
}
break;
}
}
catch (Exception e)
}
catch (Exception e)
{
if (!OnDirectoryFailure(directory, e))
{
if (!OnDirectoryFailure(directory, e))
{
throw;
}
throw;
}
}

}

#region Instance Fields
92 changes: 21 additions & 71 deletions src/ICSharpCode.SharpZipLib/Zip/FastZip.cs
Original file line number Diff line number Diff line change
@@ -206,7 +206,7 @@ public FastZip()
public FastZip(TimeSetting timeSetting)
{
entryFactory_ = new ZipEntryFactory(timeSetting);
restoreDateTimeOnExtract_ = true;
RestoreDateTimeOnExtract = true;
}

/// <summary>
@@ -216,7 +216,7 @@ public FastZip(TimeSetting timeSetting)
public FastZip(DateTime time)
{
entryFactory_ = new ZipEntryFactory(time);
restoreDateTimeOnExtract_ = true;
RestoreDateTimeOnExtract = true;
}

/// <summary>
@@ -235,20 +235,12 @@ public FastZip(FastZipEvents events)
/// <summary>
/// Get/set a value indicating whether empty directories should be created.
/// </summary>
public bool CreateEmptyDirectories
{
get { return createEmptyDirectories_; }
set { createEmptyDirectories_ = value; }
}
public bool CreateEmptyDirectories { get; set; }

/// <summary>
/// Get / set the password value.
/// </summary>
public string Password
{
get { return password_; }
set { password_ = value; }
}
public string Password { get; set; }

/// <summary>
/// Get / set the method of encrypting entries.
@@ -265,30 +257,17 @@ public string Password
/// <seealso cref="EntryFactory"></seealso>
public INameTransform NameTransform
{
get { return entryFactory_.NameTransform; }
set
{
entryFactory_.NameTransform = value;
}
get => entryFactory_.NameTransform;
set => entryFactory_.NameTransform = value;
}

/// <summary>
/// Get or set the <see cref="IEntryFactory"></see> active when creating Zip files.
/// </summary>
public IEntryFactory EntryFactory
{
get { return entryFactory_; }
set
{
if (value == null)
{
entryFactory_ = new ZipEntryFactory();
}
else
{
entryFactory_ = value;
}
}
get => entryFactory_;
set => entryFactory_ = value ?? new ZipEntryFactory();
}

/// <summary>
@@ -302,48 +281,26 @@ public IEntryFactory EntryFactory
/// NOTE: Setting the size for entries before they are added is the best solution!
/// By default the EntryFactory used by FastZip will set the file size.
/// </remarks>
public UseZip64 UseZip64
{
get { return useZip64_; }
set { useZip64_ = value; }
}
public UseZip64 UseZip64 { get; set; } = UseZip64.Dynamic;

/// <summary>
/// Get/set a value indicating whether file dates and times should
/// be restored when extracting files from an archive.
/// </summary>
/// <remarks>The default value is false.</remarks>
public bool RestoreDateTimeOnExtract
{
get
{
return restoreDateTimeOnExtract_;
}
set
{
restoreDateTimeOnExtract_ = value;
}
}
public bool RestoreDateTimeOnExtract { get; set; }

/// <summary>
/// Get/set a value indicating whether file attributes should
/// be restored during extract operations
/// </summary>
public bool RestoreAttributesOnExtract
{
get { return restoreAttributesOnExtract_; }
set { restoreAttributesOnExtract_ = value; }
}
public bool RestoreAttributesOnExtract { get; set; }

/// <summary>
/// Get/set the Compression Level that will be used
/// when creating the zip
/// </summary>
public Deflater.CompressionLevel CompressionLevel
{
get { return compressionLevel_; }
set { compressionLevel_ = value; }
}
public Deflater.CompressionLevel CompressionLevel { get; set; } = CompressionLevel.DEFAULT_COMPRESSION;

/// <summary>
/// Reflects the opposite of the internal <see cref="StringCodec.ForceZipLegacyEncoding"/>, setting it to <c>false</c> overrides the encoding used for reading and writing zip entries
@@ -474,7 +431,7 @@ public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse,
/// <param name="scanner">For performing the actual file system scan</param>
/// <param name="leaveOpen">true to leave <paramref name="outputStream"/> open after the zip has been created, false to dispose it.</param>
/// <remarks>The <paramref name="outputStream"/> is closed after creation.</remarks>
private void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, FileSystemScanner scanner, bool leaveOpen)
public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, FileSystemScanner scanner, bool leaveOpen)
{
NameTransform = new ZipNameTransform(sourceDirectory);
sourceDirectory_ = sourceDirectory;
@@ -485,9 +442,9 @@ private void CreateZip(Stream outputStream, string sourceDirectory, bool recurse
outputStream_.IsStreamOwner = !leaveOpen;
outputStream_.NameTransform = null; // all required transforms handled by us

if (false == string.IsNullOrEmpty(password_) && EntryEncryptionMethod != ZipEncryptionMethod.None)
if (false == string.IsNullOrEmpty(Password) && EntryEncryptionMethod != ZipEncryptionMethod.None)
{
outputStream_.Password = password_;
outputStream_.Password = Password;
}

outputStream_.UseZip64 = UseZip64;
@@ -526,7 +483,7 @@ private void CreateZip(Stream outputStream, string sourceDirectory, bool recurse
/// <param name="fileFilter">A filter to apply to files.</param>
public void ExtractZip(string zipFileName, string targetDirectory, string fileFilter)
{
ExtractZip(zipFileName, targetDirectory, Overwrite.Always, null, fileFilter, null, restoreDateTimeOnExtract_);
ExtractZip(zipFileName, targetDirectory, Overwrite.Always, null, fileFilter, null, RestoreDateTimeOnExtract);
}

/// <summary>
@@ -577,13 +534,13 @@ public void ExtractZip(Stream inputStream, string targetDirectory,

fileFilter_ = new NameFilter(fileFilter);
directoryFilter_ = new NameFilter(directoryFilter);
restoreDateTimeOnExtract_ = restoreDateTime;
RestoreDateTimeOnExtract = restoreDateTime;

using (zipFile_ = new ZipFile(inputStream, !isStreamOwner))
{
if (password_ != null)
if (Password != null)
{
zipFile_.Password = password_;
zipFile_.Password = Password;
}

System.Collections.IEnumerator enumerator = zipFile_.GetEnumerator();
@@ -784,7 +741,7 @@ private void ExtractFileEntry(ZipEntry entry, string targetName)
}
}

if (restoreDateTimeOnExtract_)
if (RestoreDateTimeOnExtract)
{
switch (entryFactory_.Setting)
{
@@ -890,7 +847,7 @@ private void ExtractEntry(ZipEntry entry)
if (continueRunning_)
{
Directory.CreateDirectory(dirName);
if (entry.IsDirectory && restoreDateTimeOnExtract_)
if (entry.IsDirectory && RestoreDateTimeOnExtract)
{
switch (entryFactory_.Setting)
{
@@ -986,18 +943,11 @@ private static bool NameIsValid(string name)
private Overwrite overwrite_;
private ConfirmOverwriteDelegate confirmDelegate_;

private bool restoreDateTimeOnExtract_;
private bool restoreAttributesOnExtract_;
private bool createEmptyDirectories_;
private FastZipEvents events_;
private IEntryFactory entryFactory_ = new ZipEntryFactory();
private INameTransform extractNameTransform_;
private UseZip64 useZip64_ = UseZip64.Dynamic;
private CompressionLevel compressionLevel_ = CompressionLevel.DEFAULT_COMPRESSION;
private StringCodec _stringCodec = new StringCodec();

private string password_;

#endregion Instance Fields
}
}