Как получить сжатый размер (ZipArchive) блока FileStream?
Я пишу небольшой редактор пакетов appx для C# (appx - это, по сути, zip-файл, содержащий кучу файлов метаданных XML).
Чтобы создать действительный файл appx, мне нужно создать файл карты блоков (XML), который содержит для каждого файла два атрибута: хэш и размер, как описано здесь ( https://docs.microsoft.com/en-us/uwp/schemas/blockmapschema/element-block)
Хэш представляет собой несжатый фрагмент файла размером 64 КБ. Размер представляет размер этого фрагмента после сжатия (алгоритм deflate). Вот что я написал в качестве доказательства концепции:
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
namespace StreamTest
{
class Program
{
static void Main(string[] args)
{
using (var srcFile = File.OpenRead(@"C:\Test\sample.txt"))
{
ZipAndHash(srcFile);
}
Console.ReadLine();
}
static void ZipAndHash(Stream inStream)
{
const int blockSize = 65536; //64KB
var uncompressedBuffer = new byte[blockSize];
int bytesRead;
int totalBytesRead = 0;
//Create a ZIP file
using (FileStream zipStream = new FileStream(@"C:\Test\test.zip", FileMode.Create))
{
using (ZipArchive zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create))
{
using (BinaryWriter zipWriter = new BinaryWriter(zipArchive.CreateEntry("test.txt").Open()))
{
//Read stream with a 64kb buffer
while ((bytesRead = inStream.Read(uncompressedBuffer, 0, uncompressedBuffer.Length)) > 0)
{
totalBytesRead = totalBytesRead + bytesRead;
//Compress current block to the Zip entry
if (uncompressedBuffer.Length == bytesRead)
{
//Hash each 64kb block before compression
hashBlock(uncompressedBuffer);
//Compress
zipWriter.Write(uncompressedBuffer);
}
else
{
//Hash remaining bytes and compress
hashBlock(uncompressedBuffer.Take(bytesRead).ToArray());
zipWriter.Write(uncompressedBuffer.Take(bytesRead).ToArray());
}
}
//How to obtain the size of the compressed block after invoking zipWriter.Write() ?
Console.WriteLine($"total bytes : {totalBytesRead}");
}
}
}
}
private static void hashBlock(byte[] uncompressedBuffer)
{
// hash the block
}
}
}
Я легко могу получить атрибут hash, используя буфер 64 КБ при чтении потока, мой вопрос:
Как получить сжатый размер каждого блока размером 64 КБ после использования zipWrite.Write(), возможно ли это даже с System.IO.Compression или мне следует использовать что-то еще?
1 ответ
Если ваша проблема по-прежнему актуальна, для создания контейнера вы можете использовать 2 подхода:
- Управляемые API-интерфейсы OPC Packaging, которые обеспечивают поддержку приложений, которые создают или используют файлы, совместимые с Open Packaging Conventions, называются пакетами и сами разрабатывают все конкретные вещи (общее описание здесь: https://msdn.microsoft.com/en-us/library/windows/desktop/dd371623(v=vs.85).aspx)
Для получения размера сжатия блоков на лету вы можете использовать DeflateStream и MemoryStream, как показано ниже:
private static long getCompressSize(byte[] input)
{
long length = 0;
using (MemoryStream compressedStream = new MemoryStream())
{
compressedStream.Position = 0;
using (DeflateStream compressionStream = new DeflateStream(compressedStream, CompressionLevel.Optimal, true))
{
compressionStream.Write(input, 0, input.Length);
}
length = compressedStream.Length;
}
Logger.WriteLine("input length:" + input.Length + " compressed stream: " + length);
return length;
}
- C++ API для контейнеров Appx (но в этом случае Project должен быть переписан с использованием C++ или дополнительная библиотека должна быть написана и импортирована в C# project). Основным преимуществом является то, что у него уже есть методы для создания всех необходимых частей пакета. Общее описание здесь ( https://msdn.microsoft.com/en-us/library/windows/desktop/hh446766(v=vs.85).aspx)
Решение для получения размера блока и хэша:
Интерфейс IAppxBlockMapBlock предоставляет объект только для чтения, который представляет отдельный блок в файле, содержащемся в файле карты блоков (AppxBlockMap.xml) для пакета App. Метод IAppxBlockMapFile::GetBlocks используется для возврата перечислителя для обхода и извлечения отдельных блоков файла, перечисленных в карте блоков пакета.
Интерфейс IAppxBlockMapBlock наследуется от интерфейса IUnknown и имеет следующие методы:
GetCompressedSize - извлекает сжатый размер блока.
GetHash - получает хэш-значение блока.