Skip to content

Commit

Permalink
Rsync compatible gzip compression
Browse files Browse the repository at this point in the history
  • Loading branch information
Grzegorz Blok committed Dec 5, 2019
1 parent 8e01736 commit 82e10e8
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 62 deletions.
27 changes: 27 additions & 0 deletions build/FastRsync.Compression.nuspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>FastRsyncNet.Compression</id>
<version>1.0.0</version>
<authors>Grzegorz Blok</authors>
<owners>Grzegorz Blok</owners>
<license type="expression">Apache-2.0</license>
<projectUrl>https://github.com/GrzegorzBlok/FastRsyncNet</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<tags>sync rsync synchronization compression</tags>
<description>.NET library for file compression compatible with Rsync algorithm.</description>
<dependencies>
<group targetFramework=".NETFramework4.5">
<dependency id="DotNetZip" version="1.13.4" exclude="Build,Analyzers" />
</group>

<group targetFramework=".NETStandard2.0">
<dependency id="DotNetZip" version="1.13.4" exclude="Build,Analyzers" />
</group>
</dependencies>
</metadata>
<files>
<file src="..\source\FastRsync\bin\Release\net45\FastRsync.dll" target="lib\net45\FastRsyncCompression.dll" />
<file src="..\source\FastRsync\bin\Release\netstandard2.0\FastRsync.dll" target="lib\netstandard2.0\FastRsyncCompression.dll" />
</files>
</package>
26 changes: 13 additions & 13 deletions build/FastRsync.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@
<version>2.3.1</version>
<authors>Grzegorz Blok</authors>
<owners>Grzegorz Blok</owners>
<licenseUrl>https://github.com/GrzegorzBlok/FastRsyncNet/blob/master/LICENSE</licenseUrl>
<license type="expression">Apache-2.0</license>
<projectUrl>https://github.com/GrzegorzBlok/FastRsyncNet</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<tags>sync rsync synchronization</tags>
<description>.NET library for file synchronization based on Rsync algorithm. Optimized for speed and data size to achieve best network performance.</description>
<dependencies>
<group targetFramework=".NETFramework4.5">
<dependency id="System.Data.HashFunction.xxHash" version="2.0.0" exclude="Build,Analyzers" />
<dependency id="Newtonsoft.Json" version="11.0.2" exclude="Build,Analyzers" />
</group>
<group targetFramework=".NETFramework4.5">
<dependency id="System.Data.HashFunction.xxHash" version="2.0.0" exclude="Build,Analyzers" />
<dependency id="Newtonsoft.Json" version="11.0.2" exclude="Build,Analyzers" />
</group>

<group targetFramework=".NETStandard1.3">
<dependency id="System.Data.HashFunction.xxHash" version="2.0.0" exclude="Build,Analyzers" />
<dependency id="Newtonsoft.Json" version="11.0.2" exclude="Build,Analyzers" />
</group>
<group targetFramework=".NETStandard1.3">
<dependency id="System.Data.HashFunction.xxHash" version="2.0.0" exclude="Build,Analyzers" />
<dependency id="Newtonsoft.Json" version="11.0.2" exclude="Build,Analyzers" />
</group>

<group targetFramework=".NETStandard2.0">
<dependency id="System.Data.HashFunction.xxHash" version="2.0.0" exclude="Build,Analyzers" />
<dependency id="Newtonsoft.Json" version="11.0.2" exclude="Build,Analyzers" />
</group>
<group targetFramework=".NETStandard2.0">
<dependency id="System.Data.HashFunction.xxHash" version="2.0.0" exclude="Build,Analyzers" />
<dependency id="Newtonsoft.Json" version="11.0.2" exclude="Build,Analyzers" />
</group>
</dependencies>
</metadata>
<files>
Expand Down
21 changes: 21 additions & 0 deletions source/FastRsync.Compression/FastRsync.Compression.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net45;netstandard2.0</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Grzegorz Blok</Authors>
<Company />
<PackageId>FastRsyncNet.Compression</PackageId>
<Product></Product>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<Copyright>Copyright © 2019</Copyright>
<Description>.NET library for file compression compatible with Rsync algorithm.</Description>
<PackageProjectUrl>https://github.com/GrzegorzBlok/FastRsyncNet</PackageProjectUrl>
<PackageTags>sync rsync synchronization compression</PackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DotNetZip" Version="1.13.4" />
</ItemGroup>

</Project>
67 changes: 67 additions & 0 deletions source/FastRsync.Compression/GZip.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// this implementation is influenced by pigz tool by Mark Adler: https://github.com/madler/pigz/blob/master/pigz.c
// pigz license:
/*
This software is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Mark Adler
madler@alumni.caltech.edu
*/

using System;
using System.IO;
using Ionic.Zlib;

namespace FastRsync.Compression
{
public class GZip
{
const int RSYNCBITS = 12;
const uint RSYNCMASK = ((1U << RSYNCBITS) - (uint)1);
const uint RSYNCHIT = (RSYNCMASK >> 1);

const int BUFFER_SIZE = 32 * 1024;

public static void Compress(Stream sourceStream, Stream destStream)
{
var buffer = new byte[BUFFER_SIZE];

using (var compressor = new GZipStream(destStream, Ionic.Zlib.CompressionMode.Compress,
CompressionLevel.BestSpeed, true))
{
compressor.LastModified = new DateTime(1970, 1, 1);
uint hash = RSYNCHIT;
int n;
while ((n = sourceStream.Read(buffer, 0, BUFFER_SIZE)) > 0)
{
for (int i = 0, j = 0; i < n; i++)
{
hash = ((hash << 1) ^ buffer[i]) & RSYNCMASK;

if (hash == RSYNCHIT)
{
compressor.FlushMode = FlushType.Sync;
compressor.Write(buffer, j, i + 1 - j);
j = i + 1;
}
else if (i + 1 == n)
{
compressor.FlushMode = FlushType.None;
compressor.Write(buffer, j, i + 1 - j);
}
}
}
}
}
}
}
7 changes: 4 additions & 3 deletions source/FastRsync.Tests/FastRsync.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\FastRsync.Compression\FastRsync.Compression.csproj" />
<ProjectReference Include="..\FastRsync\FastRsync.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NSubstitute" Version="3.1.0" />
<PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<PackageReference Include="NSubstitute" Version="4.2.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Data.DataSetExtensions" />
Expand Down
102 changes: 102 additions & 0 deletions source/FastRsync.Tests/GZipTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System;
using System.IO;
using System.IO.Compression;
using FastRsync.Compression;
using FastRsync.Core;
using FastRsync.Delta;
using FastRsync.Signature;
using NUnit.Framework;

namespace FastRsync.Tests
{
[TestFixture]
public class GZipTests
{
[Test]
[TestCase(120)]
[TestCase(5 * 1024 * 1024)]
public void GZip_CompressData(int dataLength)
{
// Arrange
var data = new byte[dataLength];
new Random().NextBytes(data);
var srcStream = new MemoryStream(data);
var destStream = new MemoryStream();

// Act
GZip.Compress(srcStream, destStream);

// Assert
destStream.Seek(0, SeekOrigin.Begin);
var decompressedStream = new MemoryStream();
using (var gz = new GZipStream(destStream, CompressionMode.Decompress))
{
gz.CopyTo(decompressedStream);
}

var dataOutput = decompressedStream.ToArray();
Assert.AreEqual(data, dataOutput);
}

[Theory]
[TestCase(120)]
[TestCase(5 * 1024 * 1024)]
public void GZip_CompressData_RsyncSignatureAndPatch(int dataLength)
{
// Arrange
var dataBasis = new byte[dataLength];
new Random().NextBytes(dataBasis);
var basisStream = new MemoryStream(dataBasis);
var basisStreamCompressed = new MemoryStream();
var basisStreamCompressedSignature = new MemoryStream();

var newFileStream = new MemoryStream();
newFileStream.Write(dataBasis, 10, dataLength * 4 / 5);
var newRandomData = new byte[dataLength * 2 / 5];
new Random().NextBytes(newRandomData);
newFileStream.Write(newRandomData, 0, newRandomData.Length);
newFileStream.Seek(0, SeekOrigin.Begin);

var newFileStreamCompressed = new MemoryStream();
var deltaStream = new MemoryStream();
var patchedCompressedStream = new MemoryStream();

// Act
GZip.Compress(basisStream, basisStreamCompressed);
basisStreamCompressed.Seek(0, SeekOrigin.Begin);

var signatureBuilder = new SignatureBuilder();
signatureBuilder.Build(basisStreamCompressed, new SignatureWriter(basisStreamCompressedSignature));
basisStreamCompressedSignature.Seek(0, SeekOrigin.Begin);

GZip.Compress(newFileStream, newFileStreamCompressed);
newFileStreamCompressed.Seek(0, SeekOrigin.Begin);

var deltaBuilder = new DeltaBuilder();
deltaBuilder.BuildDelta(newFileStreamCompressed, new SignatureReader(basisStreamCompressedSignature, null),
new AggregateCopyOperationsDecorator(new BinaryDeltaWriter(deltaStream)));
deltaStream.Seek(0, SeekOrigin.Begin);

var deltaApplier = new DeltaApplier
{
SkipHashCheck = true
};
var deltaReader = new BinaryDeltaReader(deltaStream, null);
deltaApplier.Apply(basisStreamCompressed, deltaReader, patchedCompressedStream);
deltaApplier.HashCheck(deltaReader, patchedCompressedStream);

// Assert
Assert.AreEqual(newFileStreamCompressed.ToArray(), patchedCompressedStream.ToArray());

patchedCompressedStream.Seek(0, SeekOrigin.Begin);
var decompressedStream = new MemoryStream();
using (var gz = new GZipStream(patchedCompressedStream, CompressionMode.Decompress))
{
gz.CopyTo(decompressedStream);
}

var dataOutput = decompressedStream.ToArray();
Assert.AreEqual(newFileStream.ToArray(), dataOutput);
}
}
}
7 changes: 3 additions & 4 deletions source/FastRsync.Tests/PatchingBigFilesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@
using NSubstitute;
using NUnit.Framework;
using System.Security.Cryptography;
using NSubstitute.Core.SequenceChecking;

namespace FastRsync.Tests
{
[TestFixture]
public class PatchingBigFilesTests
{
[Test]
[TestCase(@"c:\repos\FastRsyncNet\source\FastRsync.Tests\bin\Debug\net472\bigfile1.bin", @"c:\repos\FastRsyncNet\source\FastRsync.Tests\bin\Debug\net472\bigfile2.bin")]
[TestCase(@"c:\bigfile1.bin", @"c:\bigfile2.bin")]
public void PatchingSyncXXHash_BigFile(string originalFileName, string newFileName)
{
try
Expand Down Expand Up @@ -71,10 +70,10 @@ public static bool CompareFilesByHash(string fileName1, string fileName2)
byte[] hash1, hash2;

using (var stream = File.OpenRead(fileName1))
hash1 = System.Security.Cryptography.MD5.Create().ComputeHash(stream);
hash1 = MD5.Create().ComputeHash(stream);

using (var stream = File.OpenRead(fileName2))
hash2 = System.Security.Cryptography.MD5.Create().ComputeHash(stream);
hash2 = MD5.Create().ComputeHash(stream);

return hash1.SequenceEqual(hash2);
}
Expand Down
12 changes: 9 additions & 3 deletions source/FastRsync.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2005
# Visual Studio Version 16
VisualStudioVersion = 16.0.29519.87
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Octodiff", "Octodiff\Octodiff.csproj", "{BBA7CDC2-DE25-4131-89F2-506712178FC8}"
EndProject
Expand All @@ -13,7 +13,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastRsync.Tests", "FastRsyn
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OctodiffAsync", "OctodiffAsync\OctodiffAsync.csproj", "{77157EEA-0EDE-4DDA-A611-D57A8F013CD2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastRsync.Benchmarks", "FastRsync.Benchmarks\FastRsync.Benchmarks.csproj", "{040122B6-214A-470A-BEB0-A2779E7EE3EC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastRsync.Benchmarks", "FastRsync.Benchmarks\FastRsync.Benchmarks.csproj", "{040122B6-214A-470A-BEB0-A2779E7EE3EC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastRsync.Compression", "FastRsync.Compression\FastRsync.Compression.csproj", "{FA7C7A6D-68E6-445C-BD19-DD1FFBB34239}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -45,6 +47,10 @@ Global
{040122B6-214A-470A-BEB0-A2779E7EE3EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{040122B6-214A-470A-BEB0-A2779E7EE3EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{040122B6-214A-470A-BEB0-A2779E7EE3EC}.Release|Any CPU.Build.0 = Release|Any CPU
{FA7C7A6D-68E6-445C-BD19-DD1FFBB34239}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA7C7A6D-68E6-445C-BD19-DD1FFBB34239}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA7C7A6D-68E6-445C-BD19-DD1FFBB34239}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA7C7A6D-68E6-445C-BD19-DD1FFBB34239}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
5 changes: 4 additions & 1 deletion source/FastRsync/FastRsync.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<PackageId>FastRsyncNet</PackageId>
<Authors>Grzegorz Blok</Authors>
<PackageLicenseUrl>https://github.com/GrzegorzBlok/FastRsyncNet/blob/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/GrzegorzBlok/FastRsyncNet</PackageProjectUrl>
<PackageTags>sync rsync synchronization</PackageTags>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand All @@ -40,4 +40,7 @@
<PackageReference Include="System.Data.HashFunction.Interfaces" Version="2.0.0" />
<PackageReference Include="System.Data.HashFunction.xxHash" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
</Project>
36 changes: 0 additions & 36 deletions source/FastRsync/Properties/AssemblyInfo.cs

This file was deleted.

Loading

0 comments on commit 82e10e8

Please sign in to comment.