Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"nxunitExplorer.nunit": "/Users/dean/.nuget/packages/nunit.consolerunner/3.10.0/tools/nunit3-console.exe",
"nxunitExplorer.modules": [
"LibZipSharp.UnitTest/bin/Debug/net471/LibZipSharp.UnitTest.dll",
]
Expand Down
9 changes: 9 additions & 0 deletions LibZipSharp.UnitTest/LibZipSharp.UnitTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@
<None Include="packaged_resources">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="info.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="characters_players.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="object_spawn.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<Target Name="RunNunitTests" DependsOnTargets="Build">
Expand Down
48 changes: 48 additions & 0 deletions LibZipSharp.UnitTest/ZipTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,54 @@ public void CheckForUnknownCompressionMethods ()
}
}

[Test]
public void InMemoryZipFile ()
{
string filePath = Path.GetFullPath ("info.json");
if (!File.Exists (filePath)) {
filePath = Path.GetFullPath (Path.Combine ("LibZipSharp.UnitTest", "info.json"));
}
string fileRoot = Path.GetDirectoryName (filePath);
using (var stream = new MemoryStream ()) {
using (var zip = ZipArchive.Create (stream)) {
zip.AddFile (filePath, "info.json");
zip.AddFile (Path.Combine (fileRoot, "characters_players.json"), "characters_players.json");
zip.AddFile (Path.Combine (fileRoot, "object_spawn.json"), "object_spawn.json");
}

stream.Position = 0;
using (var zip = ZipArchive.Open (stream)) {
Assert.AreEqual (3, zip.EntryCount);
foreach (var e in zip) {
Console.WriteLine (e.FullName);
}
zip.DeleteEntry ("info.json");
}

stream.Position = 0;
using (var zip = ZipArchive.Open (stream)) {
Assert.AreEqual (2, zip.EntryCount);
zip.AddEntry ("info.json", File.ReadAllText (filePath), Encoding.UTF8, CompressionMethod.Deflate);
}

stream.Position = 0;
using (var zip = ZipArchive.Open (stream)) {
Assert.AreEqual (3, zip.EntryCount);
Assert.IsTrue (zip.ContainsEntry ("info.json"));
var entry1 = zip.ReadEntry ("info.json");
using (var s = new MemoryStream ()) {
entry1.Extract (s);
s.Position = 0;
using (var sr = new StreamReader (s)) {
var t = sr.ReadToEnd ();
Assert.AreEqual (File.ReadAllText (filePath), t);
}

}
}
}
}

[TestCase (false)]
[TestCase (true)]
public void EnumerateSkipDeletedEntries (bool deleteFromExistingFile)
Expand Down
3 changes: 3 additions & 0 deletions LibZipSharp.UnitTest/characters_players.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"players": []
}
21 changes: 21 additions & 0 deletions LibZipSharp.UnitTest/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"version": 22,
"type": "Object",
"id": "c9bb1a03-cb8a-4f9f-b298-49ee26fa1fd9",
"ownerId": "47b9d124-6421-4055-abf4-b5fc8fbfe784",
"name": "OBJECT NAME",
"description": "",
"blockCount": 9,
"created": "2019-06-21T14:20:42.000Z",
"lastChanged": "0001-01-01T00:00:00.000Z",
"tags": [
{
"value": "some_tag",
"origin": "category"
},
{
"value": "some_other_tag",
"origin": "category"
}
]
}
30 changes: 30 additions & 0 deletions LibZipSharp.UnitTest/object_spawn.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"spawnRotationOffset":
{
"pitch": -16.274194717407227,
"yaw": -34.644023895263672,
"roll": 65.772232055664063
},
"spawnScaleOffset":
{
"x": 0.52916890382766724,
"y": 0.52916890382766724,
"z": 0.52916890382766724
},
"bounds":
{
"origin":
{
"x": -9086.74609375,
"y": -6475.60498046875,
"z": 1128.5997314453125
},
"boxExtent":
{
"x": 400.17755126953125,
"y": 474.9906005859375,
"z": 502.65240478515625
},
"sphereRadius": 530.2637939453125
}
}
2 changes: 1 addition & 1 deletion LibZipSharp.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<_LibZipSharpAssemblyVersion>2.0.1</_LibZipSharpAssemblyVersion>
<_LibZipSharpAssemblyVersion>2.0.2</_LibZipSharpAssemblyVersion>
<!--
Nuget Version. You can append things like -alpha-1 etc to this value.
But always leave the $(_LibZipSharpAssemblyVersion) value at the start.
Expand Down
2 changes: 1 addition & 1 deletion LibZipSharp/Xamarin.Tools.Zip/Native.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ public static extern IntPtr zip_source_function_create (
[MarshalAs (UnmanagedType.FunctionPtr)]zip_source_callback callback, IntPtr user_data, out zip_error_t errorp);

[DllImport (ZIP_LIBNAME, SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern UInt64 zip_source_seek_compute_offset (UInt64 offset, UInt64 length, IntPtr data, UInt64 data_length, out zip_error_t error);
public static extern Int64 zip_source_seek_compute_offset (UInt64 offset, UInt64 length, IntPtr data, UInt64 data_length, out zip_error_t error);

[DllImport (ZIP_LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern Int64 zip_dir_add (IntPtr archive, IntPtr name, OperationFlags flags);
Expand Down
87 changes: 73 additions & 14 deletions LibZipSharp/Xamarin.Tools.Zip/ZipArchive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ namespace Xamarin.Tools.Zip
/// </summary>
public abstract partial class ZipArchive : IDisposable, IEnumerable <ZipEntry>
{
internal class CallbackContext {
public Stream Source { get; set; } = null;
public Stream Destination {get; set;} = null;
public string DestinationFileName {get; set; } = null;
}
public const EntryPermissions DefaultFilePermissions = EntryPermissions.OwnerRead | EntryPermissions.OwnerWrite | EntryPermissions.GroupRead | EntryPermissions.WorldRead;
public const EntryPermissions DefaultDirectoryPermissions = EntryPermissions.OwnerAll | EntryPermissions.GroupRead | EntryPermissions.GroupExecute | EntryPermissions.WorldRead | EntryPermissions.WorldExecute;

Expand Down Expand Up @@ -87,9 +92,12 @@ internal ZipArchive (Stream stream, IPlatformOptions options, OpenFlags flags =
throw new ArgumentNullException (nameof (options));
Options = options;
Native.zip_error_t errorp;
var streamHandle = GCHandle.Alloc (stream, GCHandleType.Normal);
IntPtr h = GCHandle.ToIntPtr (streamHandle);
IntPtr source = Native.zip_source_function_create (callback, h, out errorp);
CallbackContext context = new CallbackContext () {
Source = stream,
Destination = null,
};
var contextHandle = GCHandle.Alloc (context, GCHandleType.Normal);
IntPtr source = Native.zip_source_function_create (callback, GCHandle.ToIntPtr (contextHandle), out errorp);
archive = Native.zip_open_from_source (source, flags, out errorp);
if (archive == IntPtr.Zero) {
// error;
Expand Down Expand Up @@ -323,7 +331,10 @@ public ZipEntry AddStream (Stream stream, string archivePath, EntryPermissions p
throw new ArgumentNullException (nameof (stream));
sources.Add (stream);
string destPath = EnsureArchivePath (archivePath);
var handle = GCHandle.Alloc (stream, GCHandleType.Normal);
var context = new CallbackContext () {
Source = stream,
};
var handle = GCHandle.Alloc (context, GCHandleType.Normal);
IntPtr h = GCHandle.ToIntPtr (handle);
IntPtr source = Native.zip_source_function (archive, callback, h);
long index = Native.zip_file_add (archive, destPath, source, overwriteExisting ? OperationFlags.Overwrite : OperationFlags.None);
Expand Down Expand Up @@ -758,9 +769,13 @@ internal static unsafe Int64 stream_callback (IntPtr state, IntPtr data, UInt64
var handle = GCHandle.FromIntPtr (state);
if (!handle.IsAllocated)
return -1;
var stream = handle.Target as Stream;
var context = handle.Target as CallbackContext;
if (context == null)
return -1;
var stream = context.Source;
if (stream == null)
return -1;
var destination = context.Destination ?? context.Source;
switch (cmd) {
case SourceCommand.Stat:
if (len < (UInt64)sizeof (Native.zip_stat_t))
Expand All @@ -773,34 +788,68 @@ internal static unsafe Int64 stream_callback (IntPtr state, IntPtr data, UInt64
return (Int64)sizeof (Native.zip_stat_t);

case SourceCommand.Tell:
case SourceCommand.TellWrite:
return (Int64)stream.Position;
case SourceCommand.TellWrite:
return (Int64)destination.Position;

case SourceCommand.Write:
buffer = ArrayPool<byte>.Shared.Rent (length);
try {
Marshal.Copy (data, buffer, 0, length);
stream.Write (buffer, 0, length);
destination.Write (buffer, 0, length);
return length;
} finally {
ArrayPool<byte>.Shared.Return (buffer);
}

case SourceCommand.SeekWrite:
case SourceCommand.Seek:
Native.zip_error_t error;
UInt64 offset = Native.zip_source_seek_compute_offset ((UInt64)stream.Position, (UInt64)stream.Length, data, len, out error);
stream.Seek ((long)offset, SeekOrigin.Begin);
Int64 offset = Native.zip_source_seek_compute_offset ((UInt64)destination.Position, (UInt64)destination.Length, data, len, out error);
if (offset < 0) {
return offset;
}
if (offset != stream.Seek (offset, SeekOrigin.Begin)) {
return -1;
}
break;
case SourceCommand.Seek:
offset = Native.zip_source_seek_compute_offset ((UInt64)stream.Position, (UInt64)stream.Length, data, len, out error);
if (offset < 0) {
return offset;
}
if (offset != stream.Seek (offset, SeekOrigin.Begin)) {
return -1;
}
break;

case SourceCommand.CommitWrite:
destination.Flush ();
stream.Position = 0;
destination.Position = 0;
stream.SetLength (destination.Length);
destination.CopyTo (stream);
stream.Flush ();
stream.Position = 0;
destination.Dispose ();
context.Destination = null;
if (!string.IsNullOrEmpty (context.DestinationFileName)&& File.Exists (context.DestinationFileName)) {
try {
File.Delete (context.DestinationFileName);
} catch (Exception) {
// we are deleting a temp file. So we can ignore any error.
// it will get cleaned up eventually.
Console.WriteLine ($"warning: Could not delete {context.DestinationFileName}.");
}
context.DestinationFileName = null;
}
break;
case SourceCommand.RollbackWrite:
destination.Dispose ();
context.Destination = null;
break;

case SourceCommand.Read:
if (length > stream.Length - stream.Position) {
length = (int)(stream.Length - stream.Position);
}
length = (int)Math.Min (stream.Length - stream.Position, length);
buffer = ArrayPool<byte>.Shared.Rent (length);
try {
int bytesRead = stream.Read (buffer, 0, length);
Expand All @@ -809,8 +858,18 @@ internal static unsafe Int64 stream_callback (IntPtr state, IntPtr data, UInt64
} finally {
ArrayPool<byte>.Shared.Return (buffer);
}

case SourceCommand.BeginWrite:
try {
string tempFile = Path.GetTempFileName ();
context.Destination = File.Open (tempFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);
context.DestinationFileName = tempFile;
} catch (IOException) {
// ok use a memory stream as a backup
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@grendello do you think this is a good idea? or should we just throw an exception and fail.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's fine, we should do our best to make the code work

context.Destination = new MemoryStream ();
}
destination = context.Destination;
destination.Position = 0;
break;
case SourceCommand.Open:
stream.Position = 0;
return 0;
Expand Down