29 ноября 2011 в 10:29

Пишем архиватор на основе ZLib в .NET

.NET*

Зачем пишем


  • потому что удобно иметь свой настраиваемый инструмент, в котором можно вмешаться в архивацию на любом этапе
  • потому что это интересно
  • потому что многие архиваторы имеющие api, платные, а насчет других см. первый аргумент.


Технологии и библиотеки


Понадобится библиотека zlib.net.dll (официальный сайт).
Среда разработки Visual Studio 2010
Язык C#
Framework 3.5

Техническое задание


Архиватор должен уметь:
  • сжимать файлы и каталоги
  • собирать архив без сжатия
  • шифровать данные (со сжатием и без)
  • исключать указанные пути
  • удалять файлы после их сжатия
  • распаковывать сжатый архив


Проектирование


Формат архива

Путем оптимизации пришел к следующему варианту:
Назначение
Размер
Тип архива 1 байт
Длина заголовка(после сжатия и шифрования) 4 байта
Заголовок(подробнее рассмотрим ниже) N байт
Блок содержимого первого файла N байт
Блок содержимого второго файла N байт
...... ......
Блок содержимого K-того файла N байт

Формат заголовка архива
Назначение
Размер
Размер необработанного заголовка 4 байта
Блок 1 N байт
Блок 2 N байт
...... ......
Блок K N байт

Формат блока заголовка архива
Назначение
Размер
Размер блока 4 байта
Длина абсолютного пути 4 байта
Абсолютный путь N байт
Длина относительного пути 4 байта
Относительный путь N байт
Размер объекта после обработок 8 байт


Немного пояснений. В начале файла архива хранится заголовок, в котором собраны все метаданные по объектам архива. Сам заголовок проходит те же стадии сжатия и шифрования что и файлы архива. После заголовка идут блоки хранящие содержимое файлов после обработки, блоки идут вплотную. Определение границ блока следует из заголовка, в котором хранятся размеры блоков.

Общие принципы работы


Пользователь задает опции сжатия, на основе который подключаются необходимые обработчики файлов (архиватор, шифратор), каждый такой обработчик содержит два метода, Execute и BackExecute. При архивации вызываем метод Execute, при разархивации метод BackExecute, причем при разархивации используем обработчики в обратном порядке. Такая структура позволяет крайне просто дополнить программу любым количеством новых обработчиков (например реализующих другие методы шифрования или сжатия).

Алгоритм работы


  1. Определение типа архива(сжатый, зашифрованный)
  2. Чтение списка объектов архивации
  3. Формирование на основе прочитанного списка и списка исключений полного списка архивируемых объектов
  4. Создание заголовка архива (в объектном виде)
  5. Перебор полного списка объектов лежащих в заголовке
  6. Обработка объекта, обновление данных о его размере после обработки в заголовке, запись во временный файл обработанного содержимого.
  7. Сохранение заголовка в файл
  8. Обработка заголовка(сжатие, шифрование)
  9. Сборка конечного файла архива


Реализация


ZLib умеет сжимать/распаковывать переданные ему данные в виде массива байт. Собственно это все что нам нужно и все что будет использовать. Шифровать данные он не умеет, для этого воспользуемся стандартной библиотекой .NET Framework — System.Security.Cryptography.
В процессе архивации/разархивации можно получать данные по текущему обрабатываемому объекту, а также возникшие ошибки.
В случае получения ошибки при обработке файла, пользователю предлагается на выбор 4 действия:
  • прервать выполнение
  • игнорировать ошибку
  • игнорировать все ошибки
  • повторить

Запрос действия можно отменить, просто закомментировав событие ErrorProcessing, в этом случае выполнение программы прервется.
Код программы приводить не буду, даю ссылку на исходники.

Напрямую:
Проект
В виде dll'ки

SVN:
svn://svn.code.sf.net/p/yark/code-0/trunk

Проект:
sourceforge.net/projects/yark

И пример использования:
Сжатие


ArchiveProvider compressor = new ArchiveProvider();
using (SaveFileDialog sfd = new SaveFileDialog())
{
    if (sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    {
        CompressorOption option = new CompressorOption()
        {
            Password = пароль_если_зашифровать,
            WithoutCompress = true_если_без_сжатия,
            RemoveSource = true_если_удалять_исходные_файлы,
            Output = sfd.FileName
        };
        //Списки файлов и каталогов для сжатия
        foreach (string line in lbIncludes.Items)
            option.IncludePath.Add(line);
        //Списки файлов и каталогов для исключения
        foreach (string line in lbExclude.Items)
            option.ExcludePath.Add(line);
        compressor.Compress(option);
    }
}

Разархивация


ArchiveProvider decompressor = new ArchiveProvider();
using (FolderBrowserDialog fbd = new FolderBrowserDialog())
{
    if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    {
        decompressor.Decompress(путь_к_архиву, fbd.SelectedPath, пароль_если_зашифрован);
    }
}


Сравнение результата работы


По времени результат засекать не стал, примерно одинаково.
Исходные данные:
  • каталог с текстовыми файлами (1 430 Кб)
  • каталог со смешанными данными (18 893 Кб)


Текст
Смешанные данные
WinRar
613 8 045
Zip
638 8 709
Этот
588 8 655


Для rar и zip формата был выставлен параметр обычного сжатия, который используется и в программе.
В текущем формате архива хранятся абсолютные пути файлов и каталогов, можно исключить их и немного улучшить сжатие.

Возможные улучшение


  • сохранение информации о файле (дата создания/изменения, права доступа)
  • добавить многопоточность (достаточно просто распараллелить создание временных файлов)
  • добавлять комментарии к архиву
  • ассоциировать файлы с программой
Ogoun @Ogoun
карма
60,0
рейтинг 4,0
Программист прикладного уровня
Похожие публикации
Самое читаемое Разработка

Комментарии (3)

  • 0
    Делал что-то подобное для хранения игровых ресурсов. Тоже на базе zlib, это просто. Надеюсь, что ваша статья откроет глаза, что собрать ресурсы в одном файле просто и все меньше и меньше разработчиков будут хранить ресурсы отдельными файлами.
  • 0
    Непонятно, зачем изобретать велосипед. Есть в конце-концов описание формата ZIP архива, которым можно воспользоваться.
    Ну а при изобретении своего велосипеда — не мешало бы в начало/конец файла вставлять какой-то Magic-number, определяющий формат, байта на 4.
    • 0
      >> Непонятно, зачем изобретать велосипед. Есть в конце-концов описание формата ZIP архива, которым можно воспользоваться.
      Ответ в первом абзаце, мне интересно некоторые вещи сделать самому. В посте нет ни слова о том что решение идеально или лучше других.

      >> Ну а при изобретении своего велосипеда — не мешало бы в начало/конец файла вставлять какой-то Magic-number, определяющий формат, байта на 4.
      В начале файла архива пишется 1 байт полностью определющий формат архива. Об этом тоже говорится в посте. Более того остались еще целых 6 бит для возможных нововведений в формат.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.