Как получить сжатый размер (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 подхода:

  1. Управляемые 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;
    }
  1. 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 - получает хэш-значение блока.

Другие вопросы по тегам