Skip to content

Commit 37d0c42

Browse files
Minor improvements to compression (#8555)
1 parent ac1365c commit 37d0c42

File tree

3 files changed

+105
-22
lines changed

3 files changed

+105
-22
lines changed

Common/Extensions.cs

+6-12
Original file line numberDiff line numberDiff line change
@@ -926,13 +926,10 @@ public static string GetString(this byte[] bytes, Encoding encoding = null)
926926
public static string ToMD5(this string str)
927927
{
928928
var builder = new StringBuilder(32);
929-
using (var md5Hash = MD5.Create())
929+
var data = MD5.HashData(Encoding.UTF8.GetBytes(str));
930+
for (var i = 0; i < 16; i++)
930931
{
931-
var data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(str));
932-
for (var i = 0; i < 16; i++)
933-
{
934-
builder.Append(data[i].ToStringInvariant("x2"));
935-
}
932+
builder.Append(data[i].ToStringInvariant("x2"));
936933
}
937934
return builder.ToString();
938935
}
@@ -945,13 +942,10 @@ public static string ToMD5(this string str)
945942
public static string ToSHA256(this string data)
946943
{
947944
var hash = new StringBuilder(64);
948-
using (var crypt = SHA256.Create())
945+
var crypto = SHA256.HashData(Encoding.UTF8.GetBytes(data));
946+
for (var i = 0; i < 32; i++)
949947
{
950-
var crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(data));
951-
for (var i = 0; i < 32; i++)
952-
{
953-
hash.Append(crypto[i].ToStringInvariant("x2"));
954-
}
948+
hash.Append(crypto[i].ToStringInvariant("x2"));
955949
}
956950
return hash.ToString();
957951
}

Compression/Compression.cs

+79-9
Original file line numberDiff line numberDiff line change
@@ -297,18 +297,88 @@ public static async Task<Dictionary<string, string>> UnzipDataAsync(Stream strea
297297
/// <returns>The zipped file as a byte array</returns>
298298
public static byte[] ZipBytes(byte[] bytes, string zipEntryName)
299299
{
300-
using (var memoryStream = new MemoryStream())
300+
using var memoryStream = new MemoryStream();
301+
ZipBytesAsync(memoryStream, bytes, zipEntryName, null).ConfigureAwait(false).GetAwaiter().GetResult();
302+
return memoryStream.ToArray();
303+
}
304+
305+
/// <summary>
306+
/// Performs an in memory zip of the specified bytes in the target stream
307+
/// </summary>
308+
/// <param name="target">The target stream</param>
309+
/// <param name="data">The file contents in bytes to be zipped</param>
310+
/// <param name="zipEntryName">The zip entry name</param>
311+
/// <param name="mode">The archive mode</param>
312+
/// <param name="compressionLevel">The desired compression level</param>
313+
/// <returns>The zipped file as a byte array</returns>
314+
public static async Task ZipBytesAsync(Stream target, byte[] data, string zipEntryName, ZipArchiveMode? mode = null,
315+
CompressionLevel? compressionLevel = null)
316+
{
317+
await ZipBytesAsync(target, [new KeyValuePair<byte[], string>(data, zipEntryName)], mode, compressionLevel).ConfigureAwait(false);
318+
}
319+
320+
/// <summary>
321+
/// Performs an in memory zip of the specified bytes in the target stream
322+
/// </summary>
323+
/// <param name="target">The target stream</param>
324+
/// <param name="data">The file contents in bytes to be zipped</param>
325+
/// <param name="mode">The archive mode</param>
326+
/// <param name="compressionLevel">The desired compression level</param>
327+
/// <returns>The zipped file as a byte array</returns>
328+
public static async Task ZipBytesAsync(Stream target, IEnumerable<KeyValuePair<byte[], string>> data, ZipArchiveMode? mode = null,
329+
CompressionLevel? compressionLevel = null)
330+
{
331+
compressionLevel ??= CompressionLevel.SmallestSize;
332+
using var archive = new ZipArchive(target, mode ?? ZipArchiveMode.Create, true);
333+
foreach (var kvp in data)
301334
{
302-
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
335+
var entry = archive.CreateEntry(kvp.Value, compressionLevel.Value);
336+
using var entryStream = entry.Open();
337+
await entryStream.WriteAsync(kvp.Key).ConfigureAwait(false);
338+
}
339+
}
340+
341+
/// <summary>
342+
/// Performs an in memory zip of the specified stream in the target stream
343+
/// </summary>
344+
/// <param name="target">The target stream</param>
345+
/// <param name="data">The file contents in bytes to be zipped</param>
346+
/// <param name="mode">The archive mode</param>
347+
/// <param name="compressionLevel">The desired compression level</param>
348+
/// <returns>The zipped file as a byte array</returns>
349+
public static async Task ZipStreamsAsync(string target, IEnumerable<KeyValuePair<string, Stream>> data, ZipArchiveMode? mode = null,
350+
CompressionLevel? compressionLevel = null)
351+
{
352+
using var fileStream = mode == ZipArchiveMode.Update
353+
? new FileStream(target, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)
354+
: new FileStream(target, FileMode.Create, FileAccess.Write, FileShare.None);
355+
await ZipStreamsAsync(fileStream, data, mode, compressionLevel).ConfigureAwait(false);
356+
}
357+
358+
/// <summary>
359+
/// Performs an in memory zip of the specified stream in the target stream
360+
/// </summary>
361+
/// <param name="target">The target stream</param>
362+
/// <param name="data">The file contents in bytes to be zipped</param>
363+
/// <param name="mode">The archive mode</param>
364+
/// <param name="compressionLevel">The desired compression level</param>
365+
/// <param name="leaveStreamOpen">True to leave the taget stream open</param>
366+
/// <returns>The zipped file as a byte array</returns>
367+
public static async Task ZipStreamsAsync(Stream target, IEnumerable<KeyValuePair<string, Stream>> data, ZipArchiveMode? mode = null,
368+
CompressionLevel? compressionLevel = null, bool leaveStreamOpen = false)
369+
{
370+
compressionLevel ??= CompressionLevel.SmallestSize;
371+
using var archive = new ZipArchive(target, mode ?? ZipArchiveMode.Create, leaveStreamOpen);
372+
foreach (var kvp in data)
373+
{
374+
if (archive.Mode == ZipArchiveMode.Update)
303375
{
304-
var entry = archive.CreateEntry(zipEntryName);
305-
using (var entryStream = entry.Open())
306-
{
307-
entryStream.Write(bytes, 0, bytes.Length);
308-
}
376+
var existingEntry = archive.GetEntry(kvp.Key);
377+
existingEntry?.Delete();
309378
}
310-
// 'ToArray' after disposing of 'ZipArchive' since it finishes writing all the data
311-
return memoryStream.ToArray();
379+
var entry = archive.CreateEntry(kvp.Key, compressionLevel.Value);
380+
using var entryStream = entry.Open();
381+
await kvp.Value.CopyToAsync(entryStream).ConfigureAwait(false);
312382
}
313383
}
314384

Tests/Compression/CompressionTests.cs

+20-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,25 @@ public void ReadLinesCountMatchesLineCount()
3838
Assert.AreEqual(expected, actual);
3939
}
4040

41+
[Test]
42+
public void ZipsStream()
43+
{
44+
const string zipName = "stream_entry.zip";
45+
File.Delete(zipName);
46+
const string fileContents = "this is the contents of a file!";
47+
using var memoryStream = new MemoryStream();
48+
var array = Encoding.UTF8.GetBytes(fileContents);
49+
memoryStream.Write(array);
50+
memoryStream.Position = 0;
51+
52+
QuantConnect.Compression.ZipStreamsAsync(zipName, [new ("entry", memoryStream)]).Wait();
53+
54+
using var file = File.OpenRead(zipName);
55+
using var streamReader = QuantConnect.Compression.UnzipStreamToStreamReader(file);
56+
var contents = streamReader.ReadToEnd();
57+
Assert.AreEqual(fileContents, contents);
58+
}
59+
4160
[Test]
4261
public void ZipBytes()
4362
{
@@ -61,7 +80,7 @@ public void ZipBytesReturnsByteArrayWithCorrectLength()
6180
var fileBytes = File.ReadAllBytes(file);
6281
var zippedBytes = QuantConnect.Compression.ZipBytes(fileBytes, "entry");
6382

64-
Assert.AreEqual(OS.IsWindows ? 905921 : 906121, zippedBytes.Length);
83+
Assert.AreEqual(OS.IsWindows ? 905693 : 906121, zippedBytes.Length);
6584
}
6685

6786
[Test]

0 commit comments

Comments
 (0)