Skip to content
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

Feat/fastzip timesetting #583

Merged
merged 2 commits into from Mar 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
92 changes: 90 additions & 2 deletions src/ICSharpCode.SharpZipLib/Zip/FastZip.cs
Expand Up @@ -3,6 +3,7 @@
using System;
using System.IO;
using static ICSharpCode.SharpZipLib.Zip.Compression.Deflater;
using static ICSharpCode.SharpZipLib.Zip.ZipEntryFactory;

namespace ICSharpCode.SharpZipLib.Zip
{
Expand Down Expand Up @@ -195,6 +196,29 @@ public FastZip()
{
}

/// <summary>
/// Initialise a new instance of <see cref="FastZip"/> using the specified <see cref="TimeSetting"/>
/// </summary>
/// <param name="timeSetting">The <see cref="TimeSetting">time setting</see> to use when creating or extracting <see cref="ZipEntry">Zip entries</see>.</param>
/// <remarks>Using <see cref="TimeSetting.LastAccessTime">TimeSetting.LastAccessTime</see><see cref="TimeSetting.LastAccessTimeUtc">[Utc]</see> when
/// creating an archive will set the file time to the moment of reading.
/// </remarks>
public FastZip(TimeSetting timeSetting)
{
entryFactory_ = new ZipEntryFactory(timeSetting);
restoreDateTimeOnExtract_ = true;
}

/// <summary>
/// Initialise a new instance of <see cref="FastZip"/> using the specified <see cref="DateTime"/>
/// </summary>
/// <param name="time">The time to set all <see cref="ZipEntry.DateTime"/> values for created or extracted <see cref="ZipEntry">Zip Entries</see>.</param>
public FastZip(DateTime time)
{
entryFactory_ = new ZipEntryFactory(time);
restoreDateTimeOnExtract_ = true;
}

/// <summary>
/// Initialise a new instance of <see cref="FastZip"/>
/// </summary>
Expand Down Expand Up @@ -735,7 +759,39 @@ private void ExtractFileEntry(ZipEntry entry, string targetName)

if (restoreDateTimeOnExtract_)
{
File.SetLastWriteTime(targetName, entry.DateTime);
switch (entryFactory_.Setting)
{
case TimeSetting.CreateTime:
File.SetCreationTime(targetName, entry.DateTime);
break;

case TimeSetting.CreateTimeUtc:
File.SetCreationTimeUtc(targetName, entry.DateTime);
break;

case TimeSetting.LastAccessTime:
File.SetLastAccessTime(targetName, entry.DateTime);
break;

case TimeSetting.LastAccessTimeUtc:
File.SetLastAccessTimeUtc(targetName, entry.DateTime);
break;

case TimeSetting.LastWriteTime:
File.SetLastWriteTime(targetName, entry.DateTime);
break;

case TimeSetting.LastWriteTimeUtc:
File.SetLastWriteTimeUtc(targetName, entry.DateTime);
break;

case TimeSetting.Fixed:
File.SetLastWriteTime(targetName, entryFactory_.FixedDateTime);
break;

default:
throw new ZipException("Unhandled time setting in ExtractFileEntry");
}
}

if (RestoreAttributesOnExtract && entry.IsDOSEntry && (entry.ExternalFileAttributes != -1))
Expand Down Expand Up @@ -809,7 +865,39 @@ private void ExtractEntry(ZipEntry entry)
Directory.CreateDirectory(dirName);
if (entry.IsDirectory && restoreDateTimeOnExtract_)
{
Directory.SetLastWriteTime(dirName, entry.DateTime);
switch (entryFactory_.Setting)
{
case TimeSetting.CreateTime:
Directory.SetCreationTime(dirName, entry.DateTime);
break;

case TimeSetting.CreateTimeUtc:
Directory.SetCreationTimeUtc(dirName, entry.DateTime);
break;

case TimeSetting.LastAccessTime:
Directory.SetLastAccessTime(dirName, entry.DateTime);
break;

case TimeSetting.LastAccessTimeUtc:
Directory.SetLastAccessTimeUtc(dirName, entry.DateTime);
break;

case TimeSetting.LastWriteTime:
Directory.SetLastWriteTime(dirName, entry.DateTime);
break;

case TimeSetting.LastWriteTimeUtc:
Directory.SetLastWriteTimeUtc(dirName, entry.DateTime);
break;

case TimeSetting.Fixed:
Directory.SetLastWriteTime(dirName, entryFactory_.FixedDateTime);
break;

default:
throw new ZipException("Unhandled time setting in ExtractEntry");
}
}
}
else
Expand Down
13 changes: 13 additions & 0 deletions src/ICSharpCode.SharpZipLib/Zip/IEntryFactory.cs
@@ -1,4 +1,6 @@
using System;
using ICSharpCode.SharpZipLib.Core;
using static ICSharpCode.SharpZipLib.Zip.ZipEntryFactory;

namespace ICSharpCode.SharpZipLib.Zip
{
Expand Down Expand Up @@ -50,5 +52,16 @@ public interface IEntryFactory
/// Get/set the <see cref="INameTransform"></see> applicable.
/// </summary>
INameTransform NameTransform { get; set; }

/// <summary>
/// Get the <see cref="TimeSetting"/> in use.
/// </summary>
TimeSetting Setting { get; }

/// <summary>
/// Get the <see cref="DateTime"/> value to use when <see cref="Setting"/> is set to <see cref="TimeSetting.Fixed"/>,
/// or if not specified, the value of <see cref="DateTime.Now"/> when the class was the initialized
/// </summary>
DateTime FixedDateTime { get; }
}
}
2 changes: 1 addition & 1 deletion src/ICSharpCode.SharpZipLib/Zip/ZipEntryFactory.cs
Expand Up @@ -364,7 +364,7 @@ public ZipEntry MakeDirectoryEntry(string directoryName, bool useFileSystem)

private INameTransform nameTransform_;
private DateTime fixedDateTime_ = DateTime.Now;
private TimeSetting timeSetting_;
private TimeSetting timeSetting_ = TimeSetting.LastWriteTime;
private bool isUnicodeText_;

private int getAttributes_ = -1;
Expand Down
175 changes: 175 additions & 0 deletions test/ICSharpCode.SharpZipLib.Tests/Zip/FastZipHandling.cs
Expand Up @@ -6,6 +6,7 @@
using System.IO;
using System.Linq;
using System.Text;
using TimeSetting = ICSharpCode.SharpZipLib.Zip.ZipEntryFactory.TimeSetting;

namespace ICSharpCode.SharpZipLib.Tests.Zip
{
Expand Down Expand Up @@ -686,5 +687,179 @@ public void CreateZipShouldLeaveOutputStreamOpenIfRequested(bool leaveOpen)
}
}
}

[Category("Zip")]
[Category("CreatesTempFile")]
[Test]
public void CreateZipShouldSetTimeOnEntriesFromConstructorDateTime()
{
var targetTime = TestTargetTime(TimeSetting.Fixed);
var fastZip = new FastZip(targetTime);
var target = CreateFastZipTestArchiveWithAnEntry(fastZip);
var archive = new MemoryStream(target.ToArray());
using (var zf = new ZipFile(archive))
{
Assert.AreEqual(targetTime, zf[0].DateTime);
}
}

[Category("Zip")]
[Category("CreatesTempFile")]
[TestCase(TimeSetting.CreateTimeUtc), TestCase(TimeSetting.LastWriteTimeUtc), TestCase(TimeSetting.LastAccessTimeUtc)]
[TestCase(TimeSetting.CreateTime), TestCase(TimeSetting.LastWriteTime), TestCase(TimeSetting.LastAccessTime)]
public void CreateZipShouldSetTimeOnEntriesFromConstructorTimeSetting(TimeSetting timeSetting)
{
var targetTime = TestTargetTime(timeSetting);
var fastZip = new FastZip(timeSetting);

var alterTime = (Action<FileInfo>) null;
switch(timeSetting)
{
case TimeSetting.LastWriteTime: alterTime = fi => fi.LastWriteTime = targetTime; break;
case TimeSetting.LastWriteTimeUtc: alterTime = fi => fi.LastWriteTimeUtc = targetTime; break;
case TimeSetting.CreateTime: alterTime = fi => fi.CreationTime = targetTime; break;
case TimeSetting.CreateTimeUtc: alterTime = fi => fi.CreationTimeUtc = targetTime; break;
}

var target = CreateFastZipTestArchiveWithAnEntry(fastZip, alterTime);
// Check that the file contents are correct in both cases
var archive = new MemoryStream(target.ToArray());
using (var zf = new ZipFile(archive))
{
Assert.AreEqual(TestTargetTime(timeSetting), zf[0].DateTime);
}
}

[Category("Zip")]
[Category("CreatesTempFile")]
[TestCase(TimeSetting.CreateTimeUtc), TestCase(TimeSetting.LastWriteTimeUtc), TestCase(TimeSetting.LastAccessTimeUtc)]
[TestCase(TimeSetting.CreateTime), TestCase(TimeSetting.LastWriteTime), TestCase(TimeSetting.LastAccessTime)]
[TestCase(TimeSetting.Fixed)]
public void ExtractZipShouldSetTimeOnFilesFromConstructorTimeSetting(TimeSetting timeSetting)
{
var targetTime = ExpectedFixedTime();
var archiveStream = CreateFastZipTestArchiveWithAnEntry(new FastZip(targetTime));

if (timeSetting == TimeSetting.Fixed)
{
Assert.Ignore("Fixed time without specifying a time is undefined");
}

var fastZip = new FastZip(timeSetting);
using (var extractDir = new Utils.TempDir())
{
fastZip.ExtractZip(archiveStream, extractDir.Fullpath, FastZip.Overwrite.Always,
_ => true, "", "", true, true, false);
var fi = new FileInfo(Path.Combine(extractDir.Fullpath, SingleEntryFileName));
Assert.AreEqual(targetTime, FileTimeFromTimeSetting(fi, timeSetting));
}
}

[Category("Zip")]
[Category("CreatesTempFile")]
[TestCase(DateTimeKind.Local), TestCase(DateTimeKind.Utc)]
public void ExtractZipShouldSetTimeOnFilesFromConstructorDateTime(DateTimeKind dtk)
{
// Create the archive with a fixed "bad" datetime
var target = CreateFastZipTestArchiveWithAnEntry(new FastZip(UnexpectedFixedTime(dtk)));

// Extract the archive with a fixed time override
var targetTime = ExpectedFixedTime(dtk);
var fastZip = new FastZip(targetTime);
using (var extractDir = new Utils.TempDir())
{
fastZip.ExtractZip(target, extractDir.Fullpath, FastZip.Overwrite.Always,
_ => true, "", "", true, true, false);
var fi = new FileInfo(Path.Combine(extractDir.Fullpath, SingleEntryFileName));
var fileTime = FileTimeFromTimeSetting(fi, TimeSetting.Fixed);
if (fileTime.Kind != dtk) fileTime = fileTime.ToUniversalTime();
Assert.AreEqual(targetTime, fileTime);
}
}

[Category("Zip")]
[Category("CreatesTempFile")]
[TestCase(DateTimeKind.Local), TestCase(DateTimeKind.Utc)]
public void ExtractZipShouldSetTimeOnFilesWithEmptyConstructor(DateTimeKind dtk)
{
// Create the archive with a fixed datetime
var targetTime = ExpectedFixedTime(dtk);
var target = CreateFastZipTestArchiveWithAnEntry(new FastZip(targetTime));

// Extract the archive with an empty constructor
var fastZip = new FastZip();
using (var extractDir = new Utils.TempDir())
{
fastZip.ExtractZip(target, extractDir.Fullpath, FastZip.Overwrite.Always,
_ => true, "", "", true, true, false);
var fi = new FileInfo(Path.Combine(extractDir.Fullpath, SingleEntryFileName));
Assert.AreEqual(targetTime, FileTimeFromTimeSetting(fi, TimeSetting.Fixed));
}
}

private static bool IsLastAccessTime(TimeSetting ts)
=> ts == TimeSetting.LastAccessTime || ts == TimeSetting.LastAccessTimeUtc;

private static DateTime FileTimeFromTimeSetting(FileInfo fi, TimeSetting timeSetting)
{
switch (timeSetting)
{
case TimeSetting.LastWriteTime: return fi.LastWriteTime;
case TimeSetting.LastWriteTimeUtc: return fi.LastWriteTimeUtc;
case TimeSetting.CreateTime: return fi.CreationTime;
case TimeSetting.CreateTimeUtc: return fi.CreationTimeUtc;
case TimeSetting.LastAccessTime: return fi.LastAccessTime;
case TimeSetting.LastAccessTimeUtc: return fi.LastAccessTimeUtc;
case TimeSetting.Fixed: return fi.LastWriteTime;
}

throw new ArgumentException("Invalid TimeSetting", nameof(timeSetting));
}

private static DateTime TestTargetTime(TimeSetting ts)
{
var dtk = ts == TimeSetting.CreateTimeUtc
|| ts == TimeSetting.LastWriteTimeUtc
|| ts == TimeSetting.LastAccessTimeUtc
? DateTimeKind.Utc
: DateTimeKind.Local;

return IsLastAccessTime(ts)
// AccessTime will be altered by reading/writing the file entry
? CurrentTime(dtk)
: ExpectedFixedTime(dtk);
}

private static DateTime CurrentTime(DateTimeKind kind)
{
var now = kind == DateTimeKind.Utc ? DateTime.UtcNow : DateTime.Now;
return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, (now.Second / 2) * 2, kind);
}

private static DateTime ExpectedFixedTime(DateTimeKind dtk = DateTimeKind.Unspecified)
=> new DateTime(2010, 5, 30, 16, 22, 50, dtk);
private static DateTime UnexpectedFixedTime(DateTimeKind dtk = DateTimeKind.Unspecified)
=> new DateTime(1980, 10, 11, 22, 39, 30, dtk);

private const string SingleEntryFileName = "testEntry.dat";

private static TrackedMemoryStream CreateFastZipTestArchiveWithAnEntry(FastZip fastZip, Action<FileInfo> alterFile = null)
{
var target = new TrackedMemoryStream();

using (var tempFolder = new Utils.TempDir())
{

// Create test input file
var addFile = Path.Combine(tempFolder.Fullpath, SingleEntryFileName);
MakeTempFile(addFile, 16);
var fi = new FileInfo(addFile);
alterFile?.Invoke(fi);

fastZip.CreateZip(target, tempFolder.Fullpath, false, SingleEntryFileName, null, leaveOpen: true);
}

return target;
}
}
}