Skip to content

Commit

Permalink
Merge pull request #63 from VahidFarahmandian/compression
Browse files Browse the repository at this point in the history
compression and decompression added
  • Loading branch information
VahidFarahmandian committed Aug 20, 2023
2 parents f213bd2 + 6fa71b4 commit 49cc816
Show file tree
Hide file tree
Showing 14 changed files with 230 additions and 26 deletions.
4 changes: 2 additions & 2 deletions 01-Core/Jinget.Core/Jinget.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.0" />
<PackageReference Include="DotNetZip" Version="1.16.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SoapFormatter" Version="1.1.9" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
<PackageReference Include="System.CodeDom" Version="6.0.0" />
Expand Down
103 changes: 103 additions & 0 deletions 01-Core/Jinget.Core/Utilities/Compression/ZipUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using Ionic.Zip;
using Ionic.Zlib;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Jinget.Core.Utilities.Compression
{
public class ZipUtility
{
/// <param name="files">each file will be compressed idependently</param>
/// <param name="maxDOP">max degree of parallelism</param>
/// <param name="eachFileMaxSize">each file will be compressed and then chunked based on this parameter. eachFileMaxSize is in MB</param>
/// <param name="path">location to store compressed files</param>
/// <param name="password">compressed files can also become password protected</param>
/// <param name="compressionLevel">It is up to you to choose between best speed to high compression</param>
/// <param name="encryption">compressed files can also become encrypted too!</param>
public async Task CompressAsync(
FileInfo[] files,
string path,
int maxDOP = 1,
int eachFileMaxSize = 5,
string password = "",
CompressionLevel compressionLevel = CompressionLevel.BestSpeed,
EncryptionAlgorithm encryption = EncryptionAlgorithm.None
)
{
if (eachFileMaxSize < 1)
eachFileMaxSize = 5;

var allTasks = new List<Task>();

var threadCount = files.Length < maxDOP ? files.Length : maxDOP;
var throttler = new SemaphoreSlim(threadCount);

foreach (var file in files)
{
await throttler.WaitAsync();
allTasks.Add(Task.Factory.StartNew(() =>
{
var fileName = Path.GetFileNameWithoutExtension(file.Name);
using ZipFile zip = new ZipFile();
if (!string.IsNullOrWhiteSpace(password))
zip.Password = password;
zip.BufferSize = eachFileMaxSize * 1024 * 1024;
zip.MaxOutputSegmentSize64 = eachFileMaxSize * 1024 * 1024;
zip.UseZip64WhenSaving = Zip64Option.AsNecessary;
zip.Encryption = encryption;
zip.CompressionLevel = compressionLevel;
zip.AddFile(file.FullName);
zip.Save(Path.Combine(path, $"{file.Name}.zip"));
throttler.Release();
}));
}

Task.WaitAll(allTasks.ToArray());
}

/// <param name="files">list of files to extract</param>
/// <param name="maxDOP">max degree of parallelism</param>
/// <param name="path">location to extract the files</param>
/// <param name="password">password used to extract the compressed files</param>
public async Task DecompressAsync(
FileInfo[] files,
string path,
int maxDOP = 1,
string password = "")
{
var allTasks = new List<Task>();
var threadCount = files.Length < maxDOP ? files.Length : maxDOP;
var throttler = new SemaphoreSlim(threadCount);

foreach (var file in files)
{
await throttler.WaitAsync();

allTasks.Add(Task.Factory.StartNew(() =>
{
using ZipFile zip1 = ZipFile.Read(file.FullName);
zip1.FlattenFoldersOnExtract = true;
foreach (ZipEntry e in zip1)
{
if (string.IsNullOrWhiteSpace(password))
{
e.Extract(path, ExtractExistingFileAction.OverwriteSilently);
}
else
e.ExtractWithPassword(path, ExtractExistingFileAction.OverwriteSilently, password);
}
throttler.Release();
}));
}
Task.WaitAll(allTasks.ToArray());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using System.Linq.Expressions;
using System.Reflection;
using Jinget.Core.ExtensionMethods.Collections;
using System.Net.NetworkInformation;

namespace Jinget.Core.Utilities.Expressions
{
Expand Down
6 changes: 4 additions & 2 deletions 01-Core/Jinget.Core/Utilities/XmlUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ public static string SerializeToXml(object input, bool omitXmlDeclaration = fals
public static string SerializeToSoapXml(object input)
{
using MemoryStream memStream = new MemoryStream();
SoapFormatter formatter = new SoapFormatter();
formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
SoapFormatter formatter = new SoapFormatter
{
AssemblyFormat = FormatterAssemblyStyle.Simple
};
formatter.Serialize(memStream, input);
return Encoding.UTF8.GetString(memStream.GetBuffer());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\01-Core\Jinget.Core\Jinget.Core.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
using Jinget.Core.Utilities;
Expand Down Expand Up @@ -34,6 +33,11 @@ internal HttpClientFactory(string baseUri, bool ignoreSslErrors = false)
client.BaseAddress = new Uri(baseUri);
}

internal HttpClientFactory(string baseUri, TimeSpan timeout, bool ignoreSslErrors = false) : this(baseUri, ignoreSslErrors)
{
client.Timeout = timeout;
}

private Uri GetUrl(string url) => new Uri($"{client.BaseAddress.ToString().TrimEnd('/')}/{url}".TrimEnd('/'));

#nullable enable
Expand Down Expand Up @@ -65,11 +69,8 @@ public async Task<HttpResponseMessage> UploadFileAsync(string url, List<FileInfo
using var multipartFormContent = new MultipartFormDataContent();
foreach (var item in files)
{
//bool isMimeTypeAvailable = MimeTypeMap.TryGetMimeType(item.Name, out var mimeType);

var st = new ByteArrayContent(await File.ReadAllBytesAsync(item.FullName));
//st.Headers.ContentType = new MediaTypeHeaderValue(isMimeTypeAvailable ? mimeType : MediaTypeNames.Application.Octet);
multipartFormContent.Add(st, "file", item.Name);
multipartFormContent.Add(st, item.Name, item.Name);
}
return await UploadFileAsync(url, multipartFormContent, headers);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Jinget.Handlers.ExternalServiceHandlers.ServiceHandler.Factory;
using System;

namespace Jinget.Handlers.ExternalServiceHandlers.ServiceHandler
{
Expand All @@ -8,10 +9,18 @@ public abstract class ServiceHandler<T> where T : new()

protected HttpClientFactory HttpClientFactory { get; set; }

protected ServiceHandler(string baseUri, bool ignoreSslErrors = false)
private ServiceHandler()
{
Events = new T();
}
protected ServiceHandler(string baseUri, bool ignoreSslErrors = false) : this()
{
HttpClientFactory = new HttpClientFactory(baseUri, ignoreSslErrors);
}

protected ServiceHandler(string baseUri, TimeSpan timeout, bool ignoreSslErrors = false) : this()
{
HttpClientFactory = new HttpClientFactory(baseUri, timeout, ignoreSslErrors);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Jinget.Core.Tests._BaseData;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
Expand Down
13 changes: 9 additions & 4 deletions Tests/Jinget.Core.Tests/Jinget.Core.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="itext7" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="itext7" Version="8.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0">
Expand All @@ -32,4 +31,10 @@
<ProjectReference Include="..\..\01-Core\Jinget.Core\Jinget.Core.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="sample.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
95 changes: 95 additions & 0 deletions Tests/Jinget.Core.Tests/Utilities/Compression/ZipUtilityTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
using System;

namespace Jinget.Core.Utilities.Compression.Tests
{
[TestClass()]
public class ZipUtilityTests
{
ZipUtility zip;
[TestInitialize]
public void Initialize() => zip = new ZipUtility();

[TestMethod()]
public async Task should_compress_and_chunkAsync()
{
FileInfo file = new("sample.txt");
int maxDOP = 5;
int eachFileMaxSize = 4;

List<FileInfo> files = new()
{
file
};
await zip.CompressAsync(files.ToArray(), files[0].DirectoryName, maxDOP, eachFileMaxSize);

Assert.IsTrue(file.Directory.GetFiles("sample.txt.z??").Any());
}

[TestMethod()]
public async Task should_compress_using_password_and_chunkAsync()
{
FileInfo file = new("sample.txt");
string password = "123";
List<FileInfo> files = new()
{
file
};
await zip.CompressAsync(files.ToArray(), files[0].DirectoryName, password: password);

Assert.IsTrue(file.Directory.GetFiles("sample.txt.z??").Any());
}

[TestMethod()]
public async Task should_decompressed_file()
{
string fileName = Guid.NewGuid() + ".txt";
using (var tw = new StreamWriter(fileName, true))
{
tw.WriteLine("sample text");
}
string password = "123";
var tobeCompressed = new FileInfo[] { new FileInfo(fileName) };
await zip.CompressAsync(tobeCompressed, tobeCompressed[0].DirectoryName, password: password);
File.Delete(fileName);

FileInfo file = new($"{fileName}.zip");
int maxDOP = 5;

List<FileInfo> files = new()
{
file
};
await zip.DecompressAsync(files.ToArray(), files[0].DirectoryName, maxDOP);

Assert.IsTrue(file.Directory.GetFiles(fileName).Any());
}

[TestMethod()]
public async Task should_decompressed_using_password()
{
string fileName = Guid.NewGuid() + ".txt";
using (var tw = new StreamWriter(fileName, true))
{
tw.WriteLine("sample text");
}
string password = "123";
var tobeCompressed = new FileInfo[] { new FileInfo(fileName) };
await zip.CompressAsync(tobeCompressed, tobeCompressed[0].DirectoryName, password: password);
File.Delete(fileName);

FileInfo file = new($"{fileName}.zip");
List<FileInfo> files = new()
{
file
};
await zip.DecompressAsync(files.ToArray(), files[0].DirectoryName, password: password);

Assert.IsTrue(file.Directory.GetFiles(fileName).Any());
}
}
}
3 changes: 0 additions & 3 deletions Tests/Jinget.Core.Tests/_BaseData/Sample.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Xml.Linq;
using System.Xml.Serialization;

namespace Jinget.Core.Tests._BaseData
{
Expand Down
1 change: 1 addition & 0 deletions Tests/Jinget.Core.Tests/sample.txt

Large diffs are not rendered by default.

Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using Jinget.Handlers.ExternalServiceHandlers.Tests.DefaultServiceHandler.SampleType;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using System.Net.Http.Headers;
using System.Net.Mime;

#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
namespace Jinget.Handlers.ExternalServiceHandlers.DefaultServiceHandler.Tests
Expand Down

0 comments on commit 49cc816

Please sign in to comment.