diff --git a/Libplanet.Tests/Store/CompressedDefaultStoreTest.cs b/Libplanet.Tests/Store/CompressedDefaultStoreTest.cs new file mode 100644 index 0000000000..311d67dbd2 --- /dev/null +++ b/Libplanet.Tests/Store/CompressedDefaultStoreTest.cs @@ -0,0 +1,12 @@ +using Xunit.Abstractions; + +namespace Libplanet.Tests.Store +{ + public class CompressedDefaultStoreTest : DefaultStoreTest + { + public CompressedDefaultStoreTest(ITestOutputHelper testOutputHelper) + : base(testOutputHelper, compress: true) + { + } + } +} diff --git a/Libplanet.Tests/Store/DefaultStoreFixture.cs b/Libplanet.Tests/Store/DefaultStoreFixture.cs index d1c7c02169..2b2b9692e2 100644 --- a/Libplanet.Tests/Store/DefaultStoreFixture.cs +++ b/Libplanet.Tests/Store/DefaultStoreFixture.cs @@ -6,7 +6,7 @@ namespace Libplanet.Tests.Store { public class DefaultStoreFixture : StoreFixture, IDisposable { - public DefaultStoreFixture(bool memory = false) + public DefaultStoreFixture(bool memory = false, bool compress = false) { if (memory) { @@ -20,7 +20,12 @@ public DefaultStoreFixture(bool memory = false) ); } - Store = new DefaultStore(Path, blockCacheSize: 2, txCacheSize: 2); + Store = new DefaultStore( + Path, + compress: compress, + blockCacheSize: 2, + txCacheSize: 2 + ); } public string Path { get; } diff --git a/Libplanet.Tests/Store/DefaultStoreTest.cs b/Libplanet.Tests/Store/DefaultStoreTest.cs index ca17cd48ed..0184af647a 100644 --- a/Libplanet.Tests/Store/DefaultStoreTest.cs +++ b/Libplanet.Tests/Store/DefaultStoreTest.cs @@ -13,10 +13,10 @@ public class DefaultStoreTest : StoreTest, IDisposable { private readonly DefaultStoreFixture _fx; - public DefaultStoreTest(ITestOutputHelper testOutputHelper) + public DefaultStoreTest(ITestOutputHelper testOutputHelper, bool compress = false) { TestOutputHelper = testOutputHelper; - Fx = _fx = new DefaultStoreFixture(); + Fx = _fx = new DefaultStoreFixture(compress: compress); } public void Dispose() diff --git a/Libplanet/Store/DefaultStore.cs b/Libplanet/Store/DefaultStore.cs index 4a8c5d4a96..56218af18f 100644 --- a/Libplanet/Store/DefaultStore.cs +++ b/Libplanet/Store/DefaultStore.cs @@ -385,6 +385,14 @@ public override Transaction GetTransaction(TxId txid) return null; } + if (_compress) + { + using (var inputBuffer = new MemoryStream(bytes)) + { + bytes = Decompress(inputBuffer); + } + } + Transaction tx = Transaction.Deserialize(bytes); _txCache.AddOrUpdate(txid, tx); return tx; @@ -819,28 +827,73 @@ internal static string FormatChainId(Guid chainId) => return null; } - RawBlock rawBlock; + IValue value; try { - var value = new Codec().Decode(_blocks.ReadAllBytes(path)); - if (!(value is Bencodex.Types.Dictionary dict)) + byte[] bytes; + if (_compress) { - throw new DecodingException( - $"Expected {typeof(Bencodex.Types.Dictionary)} but " + - $"{value.GetType()}"); + using (var f = _blocks.OpenFile(path, System.IO.FileMode.Open, FileAccess.Read)) + { + bytes = Decompress(f); + } + } + else + { + bytes = _blocks.ReadAllBytes(path); } - rawBlock = new RawBlock(dict); + value = new Codec().Decode(bytes); } catch (FileNotFoundException) { return null; } + if (!(value is Bencodex.Types.Dictionary dict)) + { + throw new DecodingException( + $"Expected {typeof(Bencodex.Types.Dictionary)} but " + + $"{value.GetType()}"); + } + + RawBlock rawBlock = new RawBlock(dict); _blockCache.AddOrUpdate(blockHash, rawBlock); return rawBlock; } + private static byte[] CompressBytes(byte[] input) + { + using (var buffer = new MemoryStream()) + using (var deflate = new DeflateStream(buffer, CompressionLevel.Fastest, true)) + { + deflate.Write(input, 0, input.Length); + deflate.Flush(); + buffer.Flush(); + int length = (int)buffer.Position; + var output = new byte[length]; + buffer.Seek(0, SeekOrigin.Begin); + buffer.Read(output, 0, output.Length); + return output; + } + } + + private static byte[] Decompress(Stream inputBuffer) + { + using (var outputBuffer = new MemoryStream()) + using (var deflate = new DeflateStream(inputBuffer, CompressionMode.Decompress)) + { + deflate.CopyTo(outputBuffer); + deflate.Flush(); + outputBuffer.Flush(); + int length = (int)outputBuffer.Position; + var output = new byte[length]; + outputBuffer.Seek(0, SeekOrigin.Begin); + outputBuffer.Read(output, 0, output.Length); + return output; + } + } + private static void CreateDirectoryRecursively(IFileSystem fs, UPath path) { if (!fs.DirectoryExists(path)) @@ -862,6 +915,11 @@ private void WriteContentAddressableFile(IFileSystem fs, UPath path, byte[] cont return; } + if (_compress) + { + contents = CompressBytes(contents); + } + // For atomicity, writes bytes into an intermediate temp file, // and then renames it to the final destination. UPath tmpPath = dirPath / $".{Guid.NewGuid():N}.tmp";