Как написать архиватор на c

Архивы в современное время играют немаловажную роль в хранении данных. Они позволяют хранить внутри себя набор разнообразных файлов. Это достаточно удобно, например, для передачи данных по сети — легче передавать один файл, чем несколько. Также архивы позволяют хранить информацию в удобной структурированной форме. Вы это, несомненно, осознавали и до этого.

В данной статье мы попробуем разработать собственный кроссплатформенный консольный архиватор с поддержкой как архивации, так и распаковки ранее запакованых файлов. Требуется уложиться в 200 строчек кода. Писать будем на C++, используя при этом лишь его стандартные библиотеки. Поэтому привязанности к определенной платформе нет — работать это будет и в Windows, и в Linux.
Почему C++, а не привычный C? Не то, что бы я имею что-то против СИ, просто в нем достаточно проблематично работать со строками и, к тому же, отсутствует ООП. Следовательно, если бы мы использовали C, то в 200 строк, думаю, вряд ли бы уложились. Ну, да ладно, приступим же!

Intro

Для начала немного теории. Думаю, все, кто читает эту статью, прекрасно знают, что любой файл — это обыкновенная последовательность байт, которой оперирует процесс, открывший файл( ресурс ). Файлы — это последовательность байт, которая приобретает различные формы в пределах файловой системы. Формы — это типы файлов, их назначения. Для операционной системы файлы — это байты, о назначении которых ей ничего не известно.
Стандартные потоки вывода также манипулируют последовательностью байт, попутно модифицируя их( это зависит от класса потока, некоторые потоки являются «сырыми», т.е работают, непосредственно, с байтами ).

Так что же требуется знать, чтобы написать архиватор? Думаю, кроме наличия некоторых знаний уровня «выше базового», касающихся C++, лишь то, что файл — это байты. И ничего более.

Codding

Пусть наш консольный архиватор будет называться Zipper`ом. Мне кажется, вполне подходящее название :)

Для работы потребуется несколько стандартных библиотек C++:

#include <iostream>
#include <string>
#include <vector>
#include <clocale>

Да, мы будем также использовать STL. Это удобно, «модно» и лаконично. STL позволит нам в разы сократить код.
Также мы будем использовать стандартное пространство имен:

using namespace std; 

Теперь разберемся с тем, как вообще будет функционировать наш архиватор. Итак, Zipper будет принимать из консоли некоторые параметры:

-pack — архивация(упаковка) файлов
-unpack — разархивация(распаковка) данных из файла-архива
-files — набор файлов для архивации( при наличии -pack ) или один файл для разархивации( при наличии -unpack )
-path — путь для сохранения разархивированных данных( при наличии -unpack ) или путь для сохранения архива ( при наличии -pack )

Распаковка данных из архива тесно связана с тем, что происходит при архивации. Архивация же будет происходить в два этапа:

1) Получение информации об архивируемых файлах
2) Создание единого файла архива, содержащего блок информации о всех файлах внутри себя и все файлы в бинарном представлении.

При архивации сначала будет происходить получении информации о всех файлах и сохраняться в промежуточный текстовый файл в таком виде:

<size_of_string>||<filesize>||<filename>||<filesize>||<filename>|| ... ||<end_of_info>||

Затем текстовый файл побайтно будет переписан в начало файла-архива и удален. Пояснения к параметрам формата информации:

size_of_string — пять байт, содержащие числа, составляющие единое число в 10-ичной системе счисления, которое указывает общий размер(в байтах) последующего блока информации( до <end_of_info> )

filesize — размер определенного файла в байтах
filename — имя этого файла.

Ну а после блока с информацией мы просто переписываем все архивируемые файлы побайтно в наш архив.
Вот, собственно, и весь процесс архивации. Теперь вернемся к коду.

Для наибольшего удобства мы создадим один класс «Zipper», который будет отвечать за все, что происходит внутри архиватора. Итак, архитектура класса » Zipper «:

class Zipper{

private:
    vector<string> files;   // набор файлов (-files)
    string path;            // путь (-path)    
    string real_bin_file;   // имя выходного файла-архива( используется при архивации )
public:
    Zipper(vector<string> &vec, string p)
    {
          if(vec.size()>0) files.assign(vec.begin(),vec.end());
          path = p+"";
          real_bin_file=path+"binary.zipper";
        }

        }
    void getInfo();   // Метод для получения информации о файлах на этапе архивации
    void InCompress();   // Архивация данных
    void OutCompress(string binary);   // Распаковка данных ( binary - путь до архива )

    // Статический метод для выделения имени файла из полного пути. 
    // Используется для внутренних нужд.
    static string get_file_name(string fn){return fn.substr(fn.find_last_of("")+1,fn.size());}
};

Нам также потребуется кое-какой простой «метод-отшельник» для подсчета количества разрядов в числе. Это потребуется, например, для записи числа в архив, как динамического буфера символов. Метод:

int digs(double w)
{
    int yield = 0;
    while(w>10) {yield++;w/=10;}
    return yield+1;
} 

Итак, мы уже имеем более-менее качественный интерфейс. Но пока все то, что было задумано, лишь в сознании.
Пора бы воплотить идею :) Реализуем метод архивации данных:

void Zipper::InCompress()
{
    char byte[1];  // единичный буфер для считывания одного байта

    getInfo();  // получаем необходимую информацию о том, что архивируем

    FILE *f;
    FILE *main=fopen((this->real_bin_file).c_str(),"wb");  // файл - архив
    FILE *info = fopen((this->path+"info.txt").c_str(),"rb");  // файл с информацией

    // переписываем информацию в архив
    while(!feof(info))
    {
        if(fread(byte,1,1,info)==1) fwrite(byte,1,1,main);
        }

        fclose(info);
        remove((this->path+"info.txt").c_str());  // прибираемся за собой

    // последовательная запись в архив архивируемых файлов побайтно :
    for(vector<string>::iterator itr=this->files.begin();itr!=this->files.end();++itr)
    {
        f = fopen((*itr).c_str(),"rb");
        if(!f){ cout<<*itr<<" не найден!"<<endl; break;}
        while(!feof(f))
        {
            if(fread(byte,1,1,f)==1) fwrite(byte,1,1,main);
        }
        cout<<*itr<<" добавлен в архив '"<<this->real_bin_file<<"'."<<endl;
        fclose(f);
    }
    fclose(main);
}

Осталось лишь реализовать наш заветный метод для получения информации. Метод » getInfo() «:

void Zipper::getInfo()
{
    char byte[1];  // единичный буфер для считывания одного байта

    basic_string<char> s_info = "";
    remove((this->path+"info.txt").c_str());  // на всякий случай
    FILE *info = fopen((this->path+"info.txt").c_str(),"a+");  // сохраняем информацию в наш текстовый файл
    int bytes_size=0;  // длина информационного блока в байтах
    for(vector<string>::iterator itr=this->files.begin();itr!=this->files.end();++itr)
    {
        FILE *f = fopen((*itr).c_str(),"rb");
        if(!f) break;

         // получаем размер архивируемого файла
        fseek(f,0,SEEK_END);
        int size = ftell(f);             

        string name = Zipper::get_file_name(*itr);  // получаем имя архивируемого файла

        char *m_size = new char[digs(size)];
        itoa(size,m_size,10);
        fclose(f);

        bytes_size+=digs(size);
        bytes_size+=strlen(name.c_str());

        // все, что "нарыли", сохраняем в промежуточный буфер :
        s_info.append(m_size);
        s_info.append("||");
        s_info.append(name);
        s_info.append("||");

        delete [] m_size;

    }
    bytes_size = s_info.size()+2;
    char *b_buff = new char[digs(bytes_size)];
    itoa(bytes_size,b_buff,10);

    // форматируем до 5 байт
    if(digs(bytes_size)<5) fputs(string(5-digs(bytes_size),'0').c_str(),info);

    fputs(b_buff,info);
    fputs("||",info);
    fputs(s_info.c_str(),info);

    fclose(info);
}

На этом с архивацией данных все. Осталось лишь добавить смысла в архивацию, а именно — распаковку того, что внутри архива. Для этого реализуем последний нереализованный метод класса Zipper — метод OutCompress(). Как говорилось в начале статьи, распаковка тесно связана с упаковкой.Эта привязанность связана с информационным блоком. Если возникнет ошибка при архивации, а именно на этапе получения информации( например, где-то просчет на 1 байт ), то весь процесс распаковки с треском рухнет. Но, этого нам, конечно же, не нужно! Мы ведь пишем валидный код. Итак, процесс распаковки состоит всего из одного этапа, содержащего два подэтапа:

1) Разборка блока с информацией о том, что содержится в архиве
2) Чтение «мессива» байт всех файлов внутри архива по правилам, указанным в информационной секции.

Что ж, реализация метода распаковки:

void Zipper::OutCompress(string binary)
{
    FILE *bin = fopen(binary.c_str(),"rb");   // открываем архив в режиме чтения
    char info_block_size[5];   // размер информационного блока 
    fread(info_block_size,1,5,bin);  // получаем размер
    int _sz = atoi(info_block_size);  // преобразуем буфер в число

    char *info_block = new char[_sz];  // информационный блок
    fread(info_block,1,_sz,bin);   // считываем его 

    // Парсинг информационного блока :
    vector<string> tokens;
    char *tok = strtok(info_block,"||");
    int toks = 0;
    while(tok)
    {
        if(strlen(tok)==0) break;
        tokens.push_back(tok);
        tok=strtok(NULL,"||");
        toks++;
    }

    if(toks%2==1) toks--;  // удаляем мусор
    int files=toks/2;  // количество обнаруженных файлов в архиве

    char byte[1];   // единичный буфер для считывания одного байта

    // Процесс распаковки всех файлов( по правилам полученным из блока с информацией ) :
    for(int i=0;i<files;i++)
    {
        const char* size = tokens[i*2].c_str();
        const char* name = tokens[i*2+1].c_str();
        char full_path[255];
        strcpy(full_path,this->path.c_str());
        strcat(full_path,name);
        int _sz = atoi(size);
        cout<<"--  '"<<name<<"' извлечен в '"<<this->path<<"' ."<<endl;
        FILE *curr = fopen(full_path,"wb");
        for(int r=1;r<=_sz;r++)
        {
            if(fread(byte,1,1,bin)==1) fwrite(byte,1,1,curr);
        }
        fclose(curr);

        delete [] size;
        delete [] name;
    }
    fclose(bin);

}

Вот, собственно, и все, что касается нашего архиватора/распаковщика. Но так как это приложение планируется использовать в консольном режиме, то нам придется подстроить наш Zipper для консоли. Нам необходимо реализовать поддержку 4 параметров, речь о которых шла в самом начале. Это делается довольно легко:

int main(int argv, char* argc[])
{
    /*/  Supported args:
    //
    //    -pack, -unpack, -files, -path
    //
    /*/

    setlocale(LC_ALL,"Russian");
    cout<<endl<<"######################## ZIPPER ########################"<<endl<<endl;
    if(argv>1)
    {
        vector<string> files;  // массив файлов, переданных через параметры из консоли
        string path = "";  // путь
        bool flag_fs = false, flag_path = false;  // флаги режима чтения/записи
        char type[6];              // тип: упаковка или распаковка 
        memset(type,0,6);

        for(int i=1;i<argv;i++)
        {
            if(strcmp(argc[i],"-pack")==0) { strcpy(type,"pack"); flag_fs=flag_path=false;}
            if(strcmp(argc[i],"-unpack")==0) { strcpy(type,"unpack"); flag_fs=flag_path=false;}
            if(strcmp(argc[i],"-path")==0) {flag_path=true; flag_fs=false; continue; }
            if(strcmp(argc[i],"-files")==0) {flag_fs=true; flag_path=false; continue; }

            if(flag_path) {path.assign(argc[i]); }
            if(flag_fs) files.push_back(string(argc[i]));

        }
        Zipper *zip = new Zipper(files,path);
        if(strcmp(type,"pack")==0) zip->InCompress();
        if(strcmp(type,"unpack")==0) zip->OutCompress(files[0]);
    }
    else cout<<"Параметры -pack/-unpack , -files, -path обязательны!"<<endl;
    cout<<endl<<"########################################################"<<endl<<endl;

}

Вот, собственно, и все приложение.

Testing

Теперь займемся неотъемлемой частью разработки — тестированием. Для наибольшего удобства я все продемонстрирую в картинках.

1) В корне локального диска «C» я создам папку с именем «test», в которой будут находиться все файлы для архивации. В «C:test» создам еще одну папку для демонстрации разархивации — «unpack»:

Создаем свой архиватор в 200 строчек кода

Далее открываем консоль и архивируем содержимое » C:test «:

Создаем свой архиватор в 200 строчек кода

Смотрим, что новенького в » C:test «:

Создаем свой архиватор в 200 строчек кода

Видим там наш архив — «binary.zipper». Откроем его с помощью какого-нибудь текстового редактора для изучения содержимого. Я это сделаю с помощью NotePad++:

Создаем свой архиватор в 200 строчек кода

Мы видим секцию с информацией и последующим мессивом байт всех файлов. Что ж, класс =)

Распакуем архив в » C:testunpack «:

Создаем свой архиватор в 200 строчек кода

Смотрим, что получилось на выходе, в » C:testunpack «:

Создаем свой архиватор в 200 строчек кода

Как можем наблюдать, все в целости и сохранности.
Ну что ж, архиватор отлично работает. Причем мы уложились в 200 строчек кода — в общем счете 190 строчек.

Outro

Как вы убедились, написать свой архиватор достаточно просто даже с базовыми знаниями C++, STL и ООП в целом. Последнее даже, скорее, лишнее, так как вполне можно обойтись без целевого класса Zipper. В таком случае код сократится еще на 15-25 строчек :)

Для тех, кому интересно посмотреть на весь код целиком, прошу перейти вот сюда.

Удачи вам в коддинге!

Автор: Asen

Источник

Предисловие

Недавно натолкнулся на один пост на Хабре, где шлось о написании собственного архиватора. В нём шла речь об dllке zlib.net.dll.
Попробовал поработать с ней — не получилось… Но на сайте автора этой библиотеки нашёл ещё одну — она называется «.NET Zip Component ZipForge.NET». Её можно скачать тут.

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

Итак, приступим:

Сжатие

using System;
using ComponentAce.Compression.ZipForge;
using ComponentAce.Compression.Archiver;
namespace ZipFilesByMask
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create an instance of the ZipForge class
            ZipForge archiver = new ZipForge();

            try
            {
                archiver.FileName = @"C:test.zip"; //Куда сохранить файл результата сжатия
                archiver.OpenArchive(System.IO.FileMode.Create); //Настраиваем дллку на работу с новым архивом
                archiver.BaseDir = @"C:"; //Папка где лежат все файлы для взятия
                archiver.AddFiles("*.exe"); //Берём все файлы с расширением exe
                archiver.AddFiles("*.dll");
                archiver.BaseDir = @"D:";
                archiver.AddFiles(@"d:file.txt"); //Добавим один файл
                archiver.AddFiles(@"d:Test"); //Запакуем ещё и папку
                archiver.CloseArchive(); //Закрываем архив
            }
            //Ловим ошибки
            catch (ArchiverException ae)
            {
                Console.WriteLine("Message: {0}t Error code: {1}",
                                  ae.Message, ae.ErrorCode);
                Console.ReadLine();
            }
        }
    }
}

Распаковка архива

using System;
// This namespace contains the main class - ZipForge
// Don't forget to add a reference to the ZipForge 
// assembly to your project references
using ComponentAce.Compression.ZipForge;
// This namespace contains ArchiverException class required for error handling
using ComponentAce.Compression.Archiver;

namespace UnzipFile
{
    class Program
    {
        static void Main(string[] args)
        {
            ZipForge archiver = new ZipForge();
            try
            {
                archiver.FileName = @"C:test.zip"; //Необходимый файл
                archiver.OpenArchive(System.IO.FileMode.Open); //Указываем что хотим сделать
                archiver.BaseDir = @"C:Temp"; //Папка куда распаковать
                archiver.ExtractFiles("*.exe"); //Распаковываем *.exe
                archiver.ExtractFiles("*.dll"); //Распаковываем *.dll
                archiver.BaseDir = @"D:";
                archiver.ExtractFiles("*.txt"); //Распаковываем *.txt но можем и просто файл
                archiver.ExtractFiles("test"); //Распаковываем папку
                archiver.CloseArchive();
            }
            // Catch all exceptions of the ArchiverException type
            catch (ArchiverException ae)
            {
                Console.WriteLine("Message: {0}t Error code: {1}", ae.Message, ae.ErrorCode);
                Console.ReadLine();
            }
        }
    }
}

Заключение

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

Достаточно часто в школе или университете преподаватели задают своим ученикам задание, связанное с написанием простого архиватора на том или ином языке программирования. Давайте рассмотрим данную задачу на примере языка C#…

Вообще создание архиватора — задача не из простых. Если говорить точнее, то вся сложность заключается в нахождении алгоритма, позволяющего эффективно сжимать данные.
Давайте рассмотрим самый простой и известный алгоритм сжатия данных.

Пусть дан файл со следующим содержимым (байты):
15 12 00 00 00 00 00 00 00 00 00 00 00 00 44 44
44 55 77 44 AF 00 00 11 11 12 45 45 00 00 00 00
01 02 03 04 05 01 02 03 04 05 06 01 02 03 04 05

Как мы видим в содержимом файла есть много повторов. А значит первое что должно прийти в голову — «нужно как-то избавиться от них». Решение данной проблемы вполне легко себе представить, ведь можно например, заменить все повторяющиеся подряд байты всего двумя. Первый будет указывать на число повторов, второй на повторяющийся байт.

Например было: 00 00 00 00 00 00 00 00
Стало: 08 00

Однако тут стоит помнить, что максимальное значение байта (FF или 255). Значит мы можем сокращать цепочки максимум из 255 одинаковых байтов. На самом деле можно сократить цепочку в 256 байтов, для этого нужно начинать нумерацию с нуля, однако я не буду этого делать (дальше станет ясно почему).

Так же стоит помнить о том, что файл нужно разархивировать, а значит нужен способ декодирования. Чтобы была возможность однозначно восставовить файл придется использовать данный метод и к неповторяющимся байтам. А это уже может повлечь за собой рост размера архива, так как один байт исходного файла заменяется двумя байтами в архиве.

В итоге для нашего исходного файла имеем:
01 15 01 12 0С 00 03 44 01 55 01 77 01 44 01 AF
02 00 02 11 01 12 02 45 04 00 01 01 01 02 01 03
01 04 01 05 01 01 01 02 01 03 01 04 01 05 01 06
01 01 01 02 01 03 01 04 01 05

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

Например было: 01 02 03 04 AA AB AC AE
Стало: 00 08 01 02 03 04 AA AB AC AE

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

Теперь применим оба правила к нашему файлу. Имеем:
00 02 15 12 0С 00 03 44 00 04 55 77 44 AF 02 00
02 11 00 01 12 02 45 04 00 00 10 01 02 03 04 05
01 02 03 04 05 06 01 02 03 04 05

Вот теперь наш файл стал меньше, чучуть, но меньше. Если подумать, то можно еще усовершенствовать данный алгоритм, например анализирую группу байт на повтор. Но этого мы делать не будет.

Сейчас же мы рассмотрим реализацию описанного метода на языке программирования C#.

Архивация файла

public static void Zip(string FileRes, string FileDest)
        {
            FileStream fs = null; //исходный файл
            FileStream rs = null; //архив
            try
            {
                if (File.Exists(FileDest))
                    File.Delete(FileDest);
                string Format = FileRes.Substring(FileRes.LastIndexOf('.') + 1); //получаем формат файла
                fs = new FileStream(FileRes, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                rs = new FileStream(FileDest, FileMode.CreateNew);
                //сначала сохраним формат исходного файла
                rs.WriteByte((byte)Format.Length);
                for (int i = 0; i < Format.Length; ++i)
                    rs.WriteByte((byte)Format[i]);

                List<byte> Bt = new List<byte>();
                List<byte> nBt = new List<byte>();
                while (fs.Position < fs.Length)
                {
                    byte B = (byte)fs.ReadByte();
                    if (Bt.Count == 0)
                        Bt.Add(B);
                    else if (Bt[Bt.Count - 1] != B)
                    {
                        //неповторяющиеся байты
                        Bt.Add(B);
                        if (Bt.Count == 255)
                        {
                            rs.WriteByte((byte)0);
                            rs.WriteByte((byte)255);
                            rs.Write(Bt.ToArray(), 0, 255);
                            Bt.Clear();
                        }
                    }
                    else
                    {
                        //повтор
                        if (Bt.Count != 1)
                        {
                            //в буфере могут быть неповторяющиеся байты
                            //их нужно сохранить
                            rs.WriteByte((byte)0);
                            rs.WriteByte((byte)(Bt.Count - 1));
                            rs.Write(Bt.ToArray(), 0, Bt.Count - 1);
                            Bt.RemoveRange(0, Bt.Count - 1);
                        }
                        Bt.Add(B);
                        while ((B = (byte)fs.ReadByte()) == Bt[0])
                        {
                            //пока идут повторы сохраняем их в буфер
                            Bt.Add(B);
                            if (Bt.Count == 255)
                            {
                                rs.WriteByte((byte)255);
                                rs.WriteByte(Bt[0]);
                                Bt.Clear();
                                break;
                            }
                        }
                        if (Bt.Count > 0)
                        {
                            //если в буфере что-то есть, сохраняем это
                            rs.WriteByte((byte)Bt.Count);
                            rs.WriteByte(Bt[0]);
                            Bt.Clear();
                            Bt.Add(B);
                        }
                    }
                }
                if (Bt.Count > 0)
                {
                    //после просмотра файла у нас может быть буфер с неповторяющимися байтами
                    rs.WriteByte((byte)0);
                    rs.WriteByte((byte)Bt.Count);
                    rs.Write(Bt.ToArray(), 0, Bt.Count);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                if (fs != null) fs.Close();
                if (rs != null) rs.Close();
            }
        }

Распаковка файла

public static void UnZip(string FileRes, string FolderDest)
        {
            if (!File.Exists(FileRes)) return;
            FileStream fs = null;
            FileStream rs = null;
            try
            {
                if (FolderDest[FolderDest.Length - 1] != '\') FolderDest += '\';
                string FileName = FileRes.Split('\')[FileRes.Split('\').Length - 1];
                string Name = FolderDest + FileName.Substring(0, FileName.LastIndexOf('.'));
                string Format = ".";
                fs = new FileStream(FileRes, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                int FormatLen = fs.ReadByte();
                for (int i = 0; i < FormatLen; ++i)
                    Format += (char)fs.ReadByte();
                if (File.Exists(Name + Format))
                    File.Delete(Name + Format);
                rs = new FileStream(Name + Format, FileMode.CreateNew);
                while (fs.Position < fs.Length)
                    {
                        int Bt = fs.ReadByte();
                        if (Bt == 0) //различные байты
                        {
                            Bt = fs.ReadByte();
                            for (int j = 0; j < Bt; ++j)
                            {
                                byte b = (byte)fs.ReadByte();
                                rs.WriteByte(b);
                            }
                        }
                        else //повторы
                        {
                            int Value = fs.ReadByte();
                            for (int j = 0; j < Bt; ++j)
                                rs.WriteByte((byte)Value);
                        }
                    }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                if (fs != null) fs.Close();
                if (rs != null) rs.Close();
            }
        }

A portable (OSX/Linux/Windows/Android/iOS), simple zip library written in C

This is done by hacking awesome miniz library and layering functions on top of the miniz v2.2.0 API.

Build

The Idea

… Some day, I was looking for zip library written in C for my project, but I could not find anything simple enough and lightweight.
Everything what I tried required ‘crazy mental gymnastics’ to integrate or had some limitations or was too heavy.
I hate frameworks, factories and adding new dependencies. If I must to install all those dependencies and link new library, I’m getting almost sick.
I wanted something powerfull and small enough, so I could add just a few files and compile them into my project.
And finally I found miniz.
Miniz is a lossless, high performance data compression library in a single source file. I only needed simple interface to append buffers or files to the current zip-entry. Thanks to this feature I’m able to merge many files/buffers and compress them on-the-fly.

It was the reason, why I decided to write zip module on top of the miniz. It required a little bit hacking and wrapping some functions, but I kept simplicity. So, you can grab these 3 files and compile them into your project. I hope that interface is also extremely simple, so you will not have any problems to understand it.

Examples

  • Create a new zip archive with default compression level.
struct zip_t *zip = zip_open("foo.zip", ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
{
    zip_entry_open(zip, "foo-1.txt");
    {
        const char *buf = "Some data here...";
        zip_entry_write(zip, buf, strlen(buf));
    }
    zip_entry_close(zip);

    zip_entry_open(zip, "foo-2.txt");
    {
        // merge 3 files into one entry and compress them on-the-fly.
        zip_entry_fwrite(zip, "foo-2.1.txt");
        zip_entry_fwrite(zip, "foo-2.2.txt");
        zip_entry_fwrite(zip, "foo-2.3.txt");
    }
    zip_entry_close(zip);
}
zip_close(zip);
  • Append to the existing zip archive.
struct zip_t *zip = zip_open("foo.zip", ZIP_DEFAULT_COMPRESSION_LEVEL, 'a');
{
    zip_entry_open(zip, "foo-3.txt");
    {
        const char *buf = "Append some data here...";
        zip_entry_write(zip, buf, strlen(buf));
    }
    zip_entry_close(zip);
}
zip_close(zip);
  • Extract a zip archive into a folder.
int on_extract_entry(const char *filename, void *arg) {
    static int i = 0;
    int n = *(int *)arg;
    printf("Extracted: %s (%d of %d)n", filename, ++i, n);

    return 0;
}

int arg = 2;
zip_extract("foo.zip", "/tmp", on_extract_entry, &arg);
  • Extract a zip entry into memory.
void *buf = NULL;
size_t bufsize;

struct zip_t *zip = zip_open("foo.zip", 0, 'r');
{
    zip_entry_open(zip, "foo-1.txt");
    {
        zip_entry_read(zip, &buf, &bufsize);
    }
    zip_entry_close(zip);
}
zip_close(zip);

free(buf);
  • Extract a zip entry into memory (no internal allocation).
unsigned char *buf;
size_t bufsize;

struct zip_t *zip = zip_open("foo.zip", 0, 'r');
{
    zip_entry_open(zip, "foo-1.txt");
    {
        bufsize = zip_entry_size(zip);
        buf = calloc(sizeof(unsigned char), bufsize);

        zip_entry_noallocread(zip, (void *)buf, bufsize);
    }
    zip_entry_close(zip);
}
zip_close(zip);

free(buf);
  • Extract a zip entry into memory using callback.
struct buffer_t {
    char *data;
    size_t size;
};

static size_t on_extract(void *arg, unsigned long long offset, const void *data, size_t size) {
    struct buffer_t *buf = (struct buffer_t *)arg;
    buf->data = realloc(buf->data, buf->size + size + 1);
    assert(NULL != buf->data);

    memcpy(&(buf->data[buf->size]), data, size);
    buf->size += size;
    buf->data[buf->size] = 0;

    return size;
}

struct buffer_t buf = {0};
struct zip_t *zip = zip_open("foo.zip", 0, 'r');
{
    zip_entry_open(zip, "foo-1.txt");
    {
        zip_entry_extract(zip, on_extract, &buf);
    }
    zip_entry_close(zip);
}
zip_close(zip);

free(buf.data);
  • Extract a zip entry into a file.
struct zip_t *zip = zip_open("foo.zip", 0, 'r');
{
    zip_entry_open(zip, "foo-2.txt");
    {
        zip_entry_fread(zip, "foo-2.txt");
    }
    zip_entry_close(zip);
}
zip_close(zip);
  • Create a new zip archive in memory (stream API).
char *outbuf = NULL;
size_t outbufsize = 0;

const char *inbuf = "Append some data here...";
struct zip_t *zip = zip_stream_open(NULL, 0, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
{
    zip_entry_open(zip, "foo-1.txt");
    {
        zip_entry_write(zip, inbuf, strlen(inbuf));
    }
    zip_entry_close(zip);

    /* copy compressed stream into outbuf */
    zip_stream_copy(zip, (void **)&outbuf, &outbufsize);
}
zip_stream_close(zip);

free(outbuf);
  • Extract a zip entry into a memory (stream API).
char *buf = NULL;
size_t bufsize = 0;

struct zip_t *zip = zip_stream_open(zipstream, zipstreamsize, 0, 'r');
{
    zip_entry_open(zip, "foo-1.txt");
    {
        zip_entry_read(zip, (void **)&buf, &bufsize);
    }
    zip_entry_close(zip);
}
zip_stream_close(zip);

free(buf);
  • List of all zip entries
struct zip_t *zip = zip_open("foo.zip", 0, 'r');
int i, n = zip_entries_total(zip);
for (i = 0; i < n; ++i) {
    zip_entry_openbyindex(zip, i);
    {
        const char *name = zip_entry_name(zip);
        int isdir = zip_entry_isdir(zip);
        unsigned long long size = zip_entry_size(zip);
        unsigned int crc32 = zip_entry_crc32(zip);
    }
    zip_entry_close(zip);
}
zip_close(zip);
  • Compress folder (recursively)
void zip_walk(struct zip_t *zip, const char *path) {
    DIR *dir;
    struct dirent *entry;
    char fullpath[MAX_PATH];
    struct stat s;

    memset(fullpath, 0, MAX_PATH);
    dir = opendir(path);
    assert(dir);

    while ((entry = readdir(dir))) {
      // skip "." and ".."
      if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
        continue;

      snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entry->d_name);
      stat(fullpath, &s);
      if (S_ISDIR(s.st_mode))
        zip_walk(zip, fullpath);
      else {
        zip_entry_open(zip, fullpath);
        zip_entry_fwrite(zip, fullpath);
        zip_entry_close(zip);
      }
    }

    closedir(dir);
}
  • Delete zip archive entries.
char *entries[] = {"unused.txt", "remove.ini", "delete.me"};

struct zip_t *zip = zip_open("foo.zip", 0, 'd');
{
    zip_entries_delete(zip, entries, 3);
}
zip_close(zip);

Bindings

Compile zip library as a dynamic library.

$ mkdir build
$ cd build
$ cmake -DBUILD_SHARED_LIBS=true ..
$ cmake --build .

Go (cgo)

package main

/*
#cgo CFLAGS: -I../src
#cgo LDFLAGS: -L. -lzip
#include <zip.h>
*/
import "C"
import "unsafe"

func main() {
	path := C.CString("/tmp/go.zip")
	zip := C.zip_open(path, 6, 'w')

	entryname := C.CString("test")
	C.zip_entry_open(zip, entryname)

	content := "test content"
	buf := unsafe.Pointer(C.CString(content))
	bufsize := C.size_t(len(content))
	C.zip_entry_write(zip, buf, bufsize)

	C.zip_entry_close(zip)

	C.zip_close(zip)
}

Rust (ffi)

extern crate libc;
use std::ffi::CString;

#[repr(C)]
pub struct Zip {
    _private: [u8; 0],
}

#[link(name = "zip")]
extern "C" {
    fn zip_open(path: *const libc::c_char, level: libc::c_int, mode: libc::c_char) -> *mut Zip;
    fn zip_close(zip: *mut Zip) -> libc::c_void;

    fn zip_entry_open(zip: *mut Zip, entryname: *const libc::c_char) -> libc::c_int;
    fn zip_entry_close(zip: *mut Zip) -> libc::c_int;
    fn zip_entry_write(
        zip: *mut Zip,
        buf: *const libc::c_void,
        bufsize: libc::size_t,
    ) -> libc::c_int;
}

fn main() {
    let path = CString::new("/tmp/rust.zip").unwrap();
    let mode: libc::c_char = 'w' as libc::c_char;

    let entryname = CString::new("test.txt").unwrap();
    let content = "test content";

    unsafe {
        let zip: *mut Zip = zip_open(path.as_ptr(), 5, mode);
        {
            zip_entry_open(zip, entryname.as_ptr());
            {
                let buf = content.as_ptr() as *const libc::c_void;
                let bufsize = content.len() as libc::size_t;
                zip_entry_write(zip, buf, bufsize);
            }
            zip_entry_close(zip);
        }
        zip_close(zip);
    }
}

Ruby (ffi)

Install ffi gem.

Bind in your module.

require 'ffi'

module Zip
  extend FFI::Library
  ffi_lib "./libzip.#{::FFI::Platform::LIBSUFFIX}"

  attach_function :zip_open, [:string, :int, :char], :pointer
  attach_function :zip_close, [:pointer], :void

  attach_function :zip_entry_open, [:pointer, :string], :int
  attach_function :zip_entry_close, [:pointer], :void
  attach_function :zip_entry_write, [:pointer, :string, :int], :int
end

ptr = Zip.zip_open("/tmp/ruby.zip", 6, "w".bytes()[0])

status = Zip.zip_entry_open(ptr, "test")

content = "test content"
status = Zip.zip_entry_write(ptr, content, content.size())

Zip.zip_entry_close(ptr)
Zip.zip_close(ptr)

Python (cffi)

Install cffi package

Bind in your package.

import ctypes.util
from cffi import FFI

ffi = FFI()
ffi.cdef("""
    struct zip_t *zip_open(const char *zipname, int level, char mode);
    void zip_close(struct zip_t *zip);

    int zip_entry_open(struct zip_t *zip, const char *entryname);
    int zip_entry_close(struct zip_t *zip);
    int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize);
""")

Zip = ffi.dlopen(ctypes.util.find_library("zip"))

ptr = Zip.zip_open("/tmp/python.zip", 6, 'w')

status = Zip.zip_entry_open(ptr, "test")

content = "test content"
status = Zip.zip_entry_write(ptr, content, len(content))

Zip.zip_entry_close(ptr)
Zip.zip_close(ptr)

Never (ffi)

extern "libzip.so" func zip_open(zipname: string, level: int, mode: char) -> c_ptr
extern "libzip.so" func zip_close(zip: c_ptr) -> void

extern "libzip.so" func zip_entry_open(zip: c_ptr, entryname: string) -> int
extern "libzip.so" func zip_entry_close(zip: c_ptr) -> int
extern "libzip.so" func zip_entry_write(zip: c_ptr, buf: string, bufsize: int) -> int
extern "libzip.so" func zip_entry_fwrite(zip: c_ptr, filename: string) -> int

func main() -> int
{
    let content = "Test content"

    let zip = zip_open("/tmp/never.zip", 6, 'w');

    zip_entry_open(zip, "test.file");
    zip_entry_fwrite(zip, "/tmp/test.txt");
    zip_entry_close(zip);

    zip_entry_open(zip, "test.content");
    zip_entry_write(zip, content, length(content));
    zip_entry_close(zip);

    zip_close(zip);
    0
}

Ring

The language comes with RingZip based on this library

load "ziplib.ring"

new Zip {
    setFileName("myfile.zip")
    open("w")
    newEntry() {
        open("test.c")
        writefile("test.c")
        close()
    }
    close()
}

Check out more cool projects which use this library:

  • Filament: Filament is a real-time physically based rendering engine for Android, iOS, Linux, macOS, Windows, and WebGL. It is designed to be as small as possible and as efficient as possible on Android.
  • Hermes JS Engine: Hermes is a JavaScript engine optimized for fast start-up of React Native apps on Android. It features ahead-of-time static optimization and compact bytecode.
  • Monster Mash: New Sketch-Based Modeling and Animation Tool.
  • Object-Oriented Graphics Rendering Engine: OGRE is a scene-oriented, flexible 3D engine written in C++ designed to make it easier and more intuitive for developers to produce games and demos utilising 3D hardware.
  • Open Asset Import Library: A library to import and export various 3d-model-formats including scene-post-processing to generate missing render data.
  • PowerToys: Set of utilities for power users to tune and streamline their Windows 10 experience for greater productivity.
  • The Ring Programming Language: Innovative and practical general-purpose multi-paradigm language.
  • The V Programming Language: Simple, fast, safe, compiled. For developing maintainable software.
  • TIC-80: TIC-80 is a FREE and OPEN SOURCE fantasy computer for making, playing and sharing tiny games.
  • Urho3D: Urho3D is a free lightweight, cross-platform 2D and 3D game engine implemented in C++ and released under the MIT license. Greatly inspired by OGRE and Horde3D.
  • Vcpkg: Vcpkg helps you manage C and C++ libraries on Windows, Linux and MacOS.
  • and more…

31 / 31 / 3

Регистрация: 18.03.2009

Сообщений: 381

Записей в блоге: 2

1

Как написать простейший архиватор?

29.07.2010, 18:54. Показов 47728. Ответов 45


Необходимо написать программу -архиватор, степень сжатия неважна, лишь бы работало. Ничего интересного и понятного в интернете не нашел, на форуме тоже. Может у кого нибудь есть простой и понятный исходник архиватора, или подскажите что и где почитать, пжл.

__________________
Помощь в написании контрольных, курсовых и дипломных работ, диссертаций здесь



0



Эксперт С++

3951 / 1806 / 184

Регистрация: 21.11.2009

Сообщений: 2,540

29.07.2010, 18:59

2

[XandeR], вам нужно смотреть в сторону Кодирования по Хаффману или Арифметического кодирования.

Информации по этим методам предостаточно (собственно, исходники тоже можно найти).



1



192 / 190 / 15

Регистрация: 27.01.2009

Сообщений: 548

29.07.2010, 19:04

3



0



31 / 31 / 3

Регистрация: 18.03.2009

Сообщений: 381

Записей в блоге: 2

29.07.2010, 19:10

 [ТС]

4

Цитата
Сообщение от radiohobbyt
Посмотреть сообщение

хм, ничего тут не нашел



0



Покинул форум

3186 / 1365 / 109

Регистрация: 29.01.2010

Сообщений: 2,887

29.07.2010, 19:28

5

[XandeR], вам нужно написать все алгоритмы сжатия самому или же нет? Просто для архивации есть множество готовых библиотек, таких как ZLib или ZipForge.



2



31 / 31 / 3

Регистрация: 18.03.2009

Сообщений: 381

Записей в блоге: 2

29.07.2010, 19:41

 [ТС]

6

да, желательно самому, в худшем случае найти несложный пример, а вот сложные библиотеки и компоненты как раз мне не нужны, меня не интересует сильное или эффективное сжатие



0



Эксперт С++

7175 / 3234 / 80

Регистрация: 17.06.2009

Сообщений: 14,164

30.07.2010, 11:29

7

Ну если самому, тогда ищи описание алгоритма Хаффмана



0



NiTan

Покинул форум

3186 / 1365 / 109

Регистрация: 29.01.2010

Сообщений: 2,887

30.07.2010, 23:33

8

[XandeR], вот здесь есть исходник на C: Исходник алгоритма Хаффмана на C.
Также посмотрите вот здесь: Алгоритм Хаффмана или LWZ — сжатие

Добавлено через 2 минуты

Цитата
Сообщение от XandeR
Посмотреть сообщение

а вот сложные библиотеки и компоненты как раз мне не нужны, меня не интересует сильное или эффективное сжатие

Да там все просто работает. Устанавливаете компонент. Затем кидаете его на форму и пишите (просто пример — реальных функций не помню):

C++
1
2
3
Zip1->FileName="Test.zip"; //Создали архив
Zip1->Open(); //Открыли
Zip1->AddFile("FileName"); //Добавили файл

Что-то вроде этого.



2



Фрилансер

3703 / 2075 / 567

Регистрация: 31.05.2009

Сообщений: 6,683

31.07.2010, 01:47

9

Давайте по порядку.

Во-первых. Исторически понятие «архивация» может означать 2 принципиально разные вещи:
1) Кодирование файла таким образом, чтобы он занимал меньше места (gzip)
2) Сохранение нескольких файлов (возможно, со структурой папок) в единый файл (tar)

Вам нужно реализовать оба этих функционала?

Во-вторых. Xаффман достаточно сложен для понимания и реализации. Есть более простые алгоритмы, тем более, если не требуется сильное сжатие. Вот, например, LZ77 с реализацией

Во-третьих. Главное, что непонятно — всё это можно найти в Гугле за несколько секунд. Поэтому не совсем понятно, какой именно помощи Вы ждете. Может быть, Вам на самом деле следует переместиться во фриланс?



0



Эксперт JavaЭксперт С++

8378 / 3600 / 419

Регистрация: 03.07.2009

Сообщений: 10,708

31.07.2010, 12:49

10

[XandeR], знаете такую историю:

Проходила какая-то олимпиада по программированию. Задача была — написать архиватор, причём чем лучше сжимает, тем больше очков — но 0 очков, если нельзя распаковать. Выиграла команда, написавшая копирование файлов.

Это я к фразе

Цитата
Сообщение от XandeR
Посмотреть сообщение

степень сжатия неважна, лишь бы работало

Задумайтесь



0



Black Fregat

31.07.2010, 17:31

Не по теме:

Цитата
Сообщение от M128K145
Посмотреть сообщение

[XandeR], знаете такую историю:

А еще была история, как студент написал упаковщик, сжимающий любой файл в 2 байта. А на резонный вопрос преподавателя об обратном преобразовании ответил, что писать распаковщик задания не было..



0



31 / 31 / 3

Регистрация: 18.03.2009

Сообщений: 381

Записей в блоге: 2

05.08.2010, 18:36

 [ТС]

12

Цитата
Сообщение от M128K145
Посмотреть сообщение

[XandeR], знаете такую историю:

забавно

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



0



Black Fregat

05.08.2010, 18:48

Не по теме:

Цитата
Сообщение от [XandeR]
Посмотреть сообщение

Black Fregat за помощь спасибо а умничать не стоит.

Вот и помогай после этого людям..



1



[XandeR]

31 / 31 / 3

Регистрация: 18.03.2009

Сообщений: 381

Записей в блоге: 2

05.08.2010, 21:32

 [ТС]

14

Алгоритм нашел.. разобрался в теории кода Хаффмана, но никак не могу понять почему мы на выходе имеем файл в 119 раз больше чем кодируемый…
алгоритм взять отсюда Исходник алгоритма Хаффмана на C
нет ну понять то я конечно могу, туда записывается туча информации, но зачем?

Добавлено через 1 час 59 минут
и ещё возник вопрос( почему в двоичный файл хоть убей нельзя записать 1 бит информации?

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 
TFileStream *out = new TFileStream("info", fmCreate); // создаем файл
if( out != NULL ) {
  AnsiString text = Memo1->Text; // исходная строка
  int size = text.Length();
  bool t=true;
 
  out->WriteBuffer(&t,  sizeof(t));
delete out;
}
 
}

записывает 1 байт, а зачем один байт под 1 бит выделять не понимаю!



0



Унылый школьник

126 / 60 / 10

Регистрация: 06.11.2009

Сообщений: 353

05.08.2010, 23:24

15

[XandeR],

Байт в современных x86-совместимых компьютерах — это минимально адресуемый набор фиксированного числа битов.

Википедия



0



31 / 31 / 3

Регистрация: 18.03.2009

Сообщений: 381

Записей в блоге: 2

06.08.2010, 16:19

 [ТС]

16

ладно, это понятно, но каким образом я смогу заархивировать текст если текстовый символ занимает 1 байт и цифра 1 или 0 тоже занимает 1 байт а после кодировки по хаффману цифр получается больше чем символов в исходном тексте, и в итоге мы получаем файл размером больше а не меньше..



0



Эксперт С++

3951 / 1806 / 184

Регистрация: 21.11.2009

Сообщений: 2,540

06.08.2010, 16:36

17

Лучший ответ Сообщение было отмечено как решение

Решение

[XandeR], рассуждения неверны.

Расскажу вам принцип кодирования по Хаффману (простого, не адаптивного).

1. Собираем статистику появления символов в заданном тексте/файле.
2. В полученной последовательности символа с его частотой встречаемости выбираем два минимальных элемента, частоту их встречаемости слаживаем и объединяем в «новый элемент» (тем самым начинается построение дерева).
3. Для «родителей» нового объекта устанавливаем «0» и «1» соответственно.
4. Повторяем пункт 2 до тех пор, пока в последовательности не останется один элемент. Соответственно, при каждом проходе не забываем устанавливать «0» и «1«.
5. Получаем последовательность бит, которыми будут закодированы символы. Для этого проходим дерево от нового (единственного) элемента к исходной последовательности.
6. Последовательность бит будет такой, что максимально повторяющийся элемент будет кодироваться минимальным количеством бит.
7. Делаем проход по тексту, заменяя символы на полученные биты, дополняем биты до байт (следующим набором бит) и записываем символ в новый файл. Последовательность из восьми бит даст один байт, который должен быть преобразован в символ.

Может теперь вы сможете увидеть, откуда берётся выигрыш в объёме.



3



31 / 31 / 3

Регистрация: 18.03.2009

Сообщений: 381

Записей в блоге: 2

06.08.2010, 17:25

 [ТС]

18

да, вижу,
но код приведенный здесь Исходник алгоритма Хаффмана на C работает неправильно, он выводит нам последовательность бит, это правильно в текстовый файл а в бинарный он выводит какую то ерунду которая занимает в 130 раз больше чем сам исходный файл. Мне достаточно того текстового файла, но ведь в него ещё надо вложить какую то информацию для раскодировки, да и вобще в алгоритме нет раскодировки..



0



Эксперт С++

3951 / 1806 / 184

Регистрация: 21.11.2009

Сообщений: 2,540

06.08.2010, 17:40

19

[XandeR], мозможно, алгоритм неверный …

Для интереса проверил… Взял файл в 50 кб.
Хаффман (не адаптивный) ужал его до 38 кб.
Арифметический метод (адаптивный) ужал до 34 кб.
WinRar ужал до 26 кб.



0



Фрилансер

3703 / 2075 / 567

Регистрация: 31.05.2009

Сообщений: 6,683

06.08.2010, 17:52

20

Цитата
Сообщение от Black Fregat
Посмотреть сообщение

Xаффман достаточно сложен для понимания и реализации. Есть более простые алгоритмы, тем более, если не требуется сильное сжатие. Вот, например, LZ77 с реализацией

Я бы даже сказал, что LZ-77 и другие алгоритмы «скользящего окна» в разы проще для понимания и реализации, но опасаюсь очередных обвинений в умствовании.

Конечно, если Хаффман так зацепил — разбирайтесь.



0



Архивация и сжатие файлов

Последнее обновление: 20.02.2022

Кроме классов чтения-записи .NET предоставляет классы, которые позволяют сжимать файлы, а также затем восстанавливать их в исходное состояние.

Это классы ZipFile, DeflateStream и GZipStream, которые находятся в пространстве имен
System.IO.Compression и представляют реализацию одного из алгоритмов сжатия Deflate или GZip.

GZipStream и DeflateStream

Для создания объекта GZipStream можно использовать один из его конструкторов:

  • GZipStream(Stream stream, CompressionLevel level): stream представляет данные, а level задает уровень сжатия
  • GZipStream(Stream stream, CompressionMode mode): mode указывает, будут ли данные сжиматься или, наоборот, восстанавливаться и может принимать два значения:
    • CompressionMode.Compress: данные сжимаются

    • CompressionMode.Decompress: данные восстанавливаются

    Если данные сжимаются, то stream указывает на поток архивируемых данных. Если данные восстанавливаются, то stream указывает на поток, куда будут передаваться восстановленные данные.

  • GZipStream(Stream stream, CompressionLevel level, bool leaveMode): параметр leaveMode указывает, надо ли оставить открытым поток stream после удаления
    объекта GZipStream. Если значение true, то поток остается открытым
  • GZipStream(Stream stream, CompressionMode mode, bool leaveMode)

Для управления сжатием/восстанавлением данных GZipStream предоставляет ряд методов. Основые из них:

  • void CopyTo(Stream destination): копирует все данные в поток destination
  • Task CopyToAsync(Stream destination): асинхронная версия метода CopyTo
  • void Flush(): очищает буфер, записывая все его данные в файл
  • Task FlushAsync(): асинхронная версия метода Flush
  • int Read(byte[] array, int offset, int count): считывает данные из файла в массив байтов и возвращает количество успешно считанных байтов.
    Принимает три параметра:

    • array — массив байтов, куда будут помещены считываемые из файла данные

    • offset представляет смещение в байтах в массиве array, в который считанные байты будут помещены

    • count — максимальное число байтов, предназначенных для чтения. Если в файле находится меньшее количество байтов, то
      все они будут считаны.

  • int Read(byte[] array, int offset, int count): считывает данные из файла в массив байтов и возвращает количество успешно считанных байтов.
    Принимает три параметра:

    • array — массив байтов, куда будут помещены считываемые из файла данные

    • offset представляет смещение в байтах в массиве array, в который считанные байты будут помещены

    • count — максимальное число байтов, предназначенных для чтения. Если в файле находится меньшее количество байтов, то
      все они будут считаны.

    • Task<int> ReadAsync(byte[] array, int offset, int count): асинхронная версия метода Read

  • long Seek(long offset, SeekOrigin origin): устанавливает позицию в потоке со смещением на количество байт,
    указанных в параметре offset.

  • void Write(byte[] array, int offset, int count): записывает в файл данные из массива байтов. Принимает три параметра:

    • array — массив байтов, откуда данные будут записываться в файл

    • offset — смещение в байтах в массиве array, откуда начинается запись байтов в поток

    • count — максимальное число байтов, предназначенных для записи

  • Task WriteAsync(byte[] array, int offset, int count): асинхронная версия метода Write

Рассмотрим применение класса GZipStream на примере:

using System.IO.Compression;

string sourceFile = "book.pdf"; // исходный файл
string compressedFile = "book.gz"; // сжатый файл
string targetFile = "book_new.pdf"; // восстановленный файл

// создание сжатого файла
await CompressAsync(sourceFile, compressedFile);
// чтение из сжатого файла
await DecompressAsync(compressedFile, targetFile);

async Task CompressAsync(string sourceFile, string compressedFile)
{
    // поток для чтения исходного файла
    using FileStream sourceStream = new FileStream(sourceFile, FileMode.OpenOrCreate);
    // поток для записи сжатого файла
    using FileStream targetStream = File.Create(compressedFile);

    // поток архивации
    using GZipStream compressionStream = new GZipStream(targetStream, CompressionMode.Compress);
    await sourceStream.CopyToAsync(compressionStream); // копируем байты из одного потока в другой

    Console.WriteLine($"Сжатие файла {sourceFile} завершено.");
    Console.WriteLine($"Исходный размер: {sourceStream.Length}  сжатый размер: {targetStream.Length}");
}

async Task DecompressAsync(string compressedFile, string targetFile)
{
    // поток для чтения из сжатого файла
    using FileStream sourceStream = new FileStream(compressedFile, FileMode.OpenOrCreate);
    // поток для записи восстановленного файла
    using FileStream targetStream = File.Create(targetFile);
    // поток разархивации
    using GZipStream decompressionStream = new GZipStream(sourceStream, CompressionMode.Decompress);
    await decompressionStream.CopyToAsync(targetStream);
    Console.WriteLine($"Восстановлен файл: {targetFile}");
}

В данном случае подразумевается, что в папке с программой располагается файл book.pdf, который собственно и будет архивироваться.

Метод CompressAsync получает название исходного файла, который надо архивировать, и название будущего сжатого файла.

Сначала создается поток для чтения из исходного файла — FileStream sourceStream. Затем создается поток для записи в сжатый файл —
FileStream targetStream. Поток архивации GZipStream compressionStream инициализируется потоком targetStream и с помощью
метода CopyToAsync() получает данные от потока sourceStream.

Метод DecompressAsync производит обратную операцию по восстановлению сжатого файла в исходное состояние. Он принимает в качестве параметров
пути к сжатому файлу и будущему восстановленному файлу.

Здесь в начале создается поток для чтения из сжатого файла FileStream sourceStream, затем поток для записи в восстанавливаемый файл
FileStream targetStream. В конце создается поток GZipStream decompressionStream, который с помощью метода
CopyToAsync() копирует восстановленные данные в поток targetStream.

Чтобы указать потоку GZipStream, для чего именно он предназначен — сжатия или восстановления — ему в конструктор передается параметр CompressionMode,
принимающий два значения: Compress и Decompress.

Пример консольного вывода программы:

Сжатие файла book.pdf завершено.
Исходный размер: 3235353  сжатый размер: 2574401
Восстановлен файл: book_new.pdf

Если бы захотели бы использовать другой класс сжатия — DeflateStream, то мы могли бы просто заменить в коде упоминания GZipStream на DeflateStream,
без изменения остального кода. Их использование идентично.

В то же время при использовании этих классов есть некоторые ограничения, в частности, мы можем сжимать только один файл. Для архивации группы файлы
лучше выбрать другие инструменты, например, ZipFile.

ZipFile

Статический класс ZipFile из простанства имен System.IO.Compression предоставляет дополнительные возможности для создания архивов. Он позволяет создавать архив из каталогов.
Его основные методы:

  • void CreateFromDirectory(string sourceDirectoryName, string destinationFileName): архивирует папку по пути sourceDirectoryName в
    файл с названием destinationFileName

  • void ExtractToDirectory(string sourceFileName, string destinationDirectoryName): извлекает все файлы из zip-файла sourceFileName в каталог destinationDirectoryName

Оба метода имеют ряд дополнительных перегруженных версий. Рассмотрим их применение.

using System.IO.Compression;

string sourceFolder = "D://test/"; // исходная папка
string zipFile = "D://test.zip"; // сжатый файл
string targetFolder = "D://newtest"; // папка, куда распаковывается файл

ZipFile.CreateFromDirectory(sourceFolder, zipFile);
Console.WriteLine($"Папка {sourceFolder} архивирована в файл {zipFile}");
ZipFile.ExtractToDirectory(zipFile, targetFolder);

Console.WriteLine($"Файл {zipFile} распакован в папку {targetFolder}");

В данном случае папка «D://test/» методом ZipFile.CreateFromDirectory архивируется в файл test.zip. Затем метод
ZipFile.ExtractToDirectory() распаковывает данный файл в папку «D://newtest» (если такой папки нет, она создается).

  • Download CYOA1 — 439.41 KB
  • Download Cake3-3183 — 370.42 KB
  • Download DllPack — 1.79 MB

Introduction

Many years ago, when I start learning how to develop software, zippware is one of my first products I developed, I developed my first prototype using a demo of DelZip.

I found that developing such a product is a good way to learn how to program window programs. To build the most basic zippware, you have to learn how to do many things using the existing framework, like User Interface, IO handling, Shell, Win32 API. Once you completed the basics, you can apply most new features that you just learned, like backup support, CD burner support.

There are many ways to develop an zippware, but if you want to develop one that supports a very wide range of archive types, you will either have to purchase an expensive commercial solutions, or write a lot of code to support each archive types. I chose to write my own code, and I released them since 2001 (named CAKE), the source code is under LGPL license, you are free to use it to develop your own archiver.

Because CAKE3 lacks documentation, I write this tutorial to explain how to write the most basic archiver using CAKE3.

This tutorial includes 3 parts:

  1. Archive Operations (this article)
  2. File List, Directory Tree and Drag n Drop
  3. Threading support

CAKE (Common Archiver Kit Experiment) History

Back in around 2001, when first developed, there were many freeware archive support components, each support a few archive formats, so CAKE is written to combine these components to support wider range of archives. Because it was developed under Delphi, users of the component have to install around 9 components before installing CAKE.

(e.g. CAKE2 <——> Cmarc<——> 7zip-32.dll)

In 2006, CAKE is being ported to dotNet environment (CAKE3.NET), the code of archive handling is rewritten, no longer requires the middle layer, and all external w32 DLL is called directly. To make the port easier, some Delphi commands (AppendSlash, ExtractFileName) are ported as well, all under Cake3.Utils class. Add files to archive using relative path is also possible in this version.

(e.g. CAKE3 <——> 7-zip32.dll)

Structure of Cake

  • Cake3 (Cake3.dll)
    • Cakdir3: Main class, most archive operation does here
    • ContentList: A list containing Content Type (you can read it as List<ContentType>)
    • ContentType: Represent a file in an archive
    • Defines: Definition of misc structures
      • Add / Delete / Extract / SFXOptions
    • Queue: For support threading, will be explained in future articles
      • CakdirThreadQueue
      • CakdirWorkItem
      • ThreadQueueMonitor
    • Utils: All tools available
    • {CakArchiver, connect other dotNet components or DLL}
      • Ace
      • Cmarc
      • IconLib
      • InstallBuild
      • Rar
      • SharpZipLib
      • Sqx
      • WcxPlugins
Please note that in the attached demo, all archive handling routines are placed in separate methods (from UI), this will make the program easier to modify.

This demo requires VS2008, if you want to use SharpDevelop, please recreate the project then add the source again.

To Open an Archive

1) Cakdir3 cakdir = new Cakdir3(archiveName); 
  • Quite simple, just noted that you have to create a new instance of Cakdir3 everytime you open a new archive.

    If the archive does not exist, it will create it when you add file(s) to archive (see below).

To List Archive Contents (to a listview named lvFileList)

1) if (cakdir == null) return;
2) cakdir.List("*");
3) foreach (ContentType ct in cakdir.Archive_Contents)
4) {
5)    ListViewItem item = new ListViewItem(new string[] 
                  {ct.fileName, Utils.SizeInK(ct.fileSize) });
6)    lvFileList.Items.Add(item); 
7) }
  • Line 2 requests cakdir3 to perform list content, you can specify other mask as well (keep in mind only * is supported, ? is not supported).
  • Line 3, cakdir.Archive_Contents is a ContentList containing zero or more ContentTypes.
  • Line 5 creates a new ListViewItem for each entry, Utils.SizeInK converts an int to kb string, (e.g. 74550000 —> «745.50 kb«)

The listing is working now, however it looks unattractive without icons, so we shall add Icon support now.

To List Archive Contents with Icons

You have to create an ImageList named (imageS, means small images) in MainForm, hook it to lvFileList.SmallIcons.

5)    ListViewItem item = new ListViewItem(new string[] 
           {ct.fileName, Utils.SizeInK(ct.fileSize) });
5.1)  item.ImageKey = Utils.ExtractFileExt(ct.fileName).ToLower();
5.2)  if (!imageS.Images.ContainsKey(item.ImageKey))
5.3)      imageS.Images.Add(item.ImageKey, Utils.GetSmallFileIcon(ct.fileName));
6)    lvFileList.Items.Add(item);  
  • Line5.1 specifies an imagekey, which is based on the extension of file (Utils.ExtractFileExt extract ext from a string)
  • Line5.2 and 5.3, if image list does not contain the imagekey, it will retrieve an icon for the specified extension and add it to image list.

    (Utils.GetSmallFileIcon() uses W32 API (SHGetFileInfo) to retrieve icon. You can find Utils.GetLargeFileIcon as well).

To Extract Files from Archive

1) if (cakdir == null) return;
2) if (!cakdir.CanExtract) return;
3) cakdir.ExtractOptions.extractItem = new string[] { "*" };
4) cakdir.ExtractOptions.extractFolder = @"c:temp"; 
5) cakdir.ExtractOptions.allowFolder = true; 
6) cakdir.ExtractOptions.extractOverwrite = true;
7) bool success = cakdir.Extract();
  • Line 2, you can use CanExtract to see if files can be extracted from specified archive, there’s also CanList / CanAdd

    if you want to check if archive without opening it, use (Cakdir3.GetArchiveType(".zip").CanExtract) instead.

  • Line 3-6, all extract related options are located in ExtractOptions
    • archiveName : name of archive, you don’t have to touch this normally.
    • allowFolder: whether use folder information
    • dialogless: some archiver DLL does show progress dialog when executing, enable this will disable the DLLs
    • extractFolder: specify where to extract.
    • extractItem: specify item to extract
    • extractOverwrite: whether to overwrite existing files
    • password: specify password
    • ResetExtractItem(): set extractItem to «*»
  • Line 7 will return true if extract is success,

    alternatively you can call the following method instead.

3) cakdir.Extract(filter, extractTo, useFolder, allowOverwrite); 

The following method can extract file recursively (e.g. «outer.zipinner.zipcore.zip«)

3) Utils.ExtractArchiveRecrusive(archiveName, extrOptions); 

To Add Files to Archive

1) if (cakdir == null) return;
2) if (!cakdir.CanAdd) return;
3) cakdir.AddOptions.addFile = addFile;
4) cakdir.AddOptions.addFolder = AddOptions.folderMode.full;
5) bool success = cakdir.Add();
6) cakdir.List("*");
  • Line 3-4, all add related options is located in AddOptions
    • addCompressLevel: 0..9, cake does not have individual compression method support yet
    • addFile: file to add
    • addFolder: how to store folder information, select one of folderMode below:
      folderMode Description

      if add «c: temp test file.txt» and set baseFolder as «c:temp»

      full Store full folder information. temp test file.txt
      none Store no folder information. file.txt
      relative Store folder information relative to baseFolder. test file.txt
    • addMode: how to add file, select one of the updateMode below:
      updateMode Description
      add Add all specified files to the archive
      refresh Update older files in the archive and add files that are new to the archive
      update Update specified files in the archive that are older than the selected disk files
      synchronize Replace if newer, add even if not present in archive, delete if not present in disk
    • archiveName: name of archive, you don’t have to touch this normally
    • baseFolder: base folder if addFolder = relative
    • dialogless: some archiver DLL does show progress dialog when executing, enable this will disable the DLLs
    • password: specify password
    • defaultOptions(): revert back to default options
  • Line 5 will return true if add is success,

    Please note that all operations (Add / Extract / Delete) are not threaded.

  • Line 6 forces cakdir3 to perform list content again.

To Delete Files from Archive

1) if (cakdir == null) return;
2) if (!cakdir.CanAdd) return;
3) cakdir.DeleteOptions.deleteFile = deleteFile;
4) cakdir.Delete();
5) cakdir.List("*"); 
  • Line 3, all delete related options is located in DeleteOptions
    • archiveName: Name of archive, you don’t have to touch this normally
    • deleteFile: File to delete
  • Line 4 will return true if add is success
  • Line 5 forces cakdir3 to perform list content again

Handling Progress Message

Let user know what’s working on.

1) cakdir.OnMessage += new MessageEventHandler(ProgressScreen_Message);
.....
2) void ProgressScreen_Message(object sender, MessageEventArgs e)
3) { tbMessage.Text += e.Message + Environment.NewLine; }
  • Line 3 will append the message to a textbox (tbMessage)

You can show display progress using a progress bar too:

1) cakdir.OnProgress += new MessageEventHandler(ProgressScreen_Progress);
.....
2) void ProgressScreen_Progress(object sender, MessageEventArgs e)
3) { pBar.Value = e.Percent; }
  • Line 3 will set the percentage of progress bar (pBar), other properties in MessageEventArgs:
    • Percent: Percentage completed (1..100)
    • Filename: File processing

Please note that progress bar is not completely working for all archivers yet.

Other events included in Cakdir3:

  • OnStartWorking / OnStopWorking: Signal when start and finish
  • OnError: Signal when error occurred
  • OnOverwrite — When extract with extractOverwrite set to false, it signals when a file already exists. (if not overridden, internal overwrite handler is used)
  • OnPassword — signal when password required
  • OnItemList — usually for internal use, when Cakdir.InternalList is on, list via this event instead of Archive_Contents

When you completed this part, you should know how to use Cake3 to do most archive operations. The next article will describe how to improve the User Interface, implement virtual file list, directory tree and drag and drop support.

References

  • Fesersoft’s CRC32 implementation
  • Using IFilter in C#
  • Implementing the .NET IComparer interface to get a more natural sort order
  • SharpZipLib
  • IconLib
  • Common Archiver Library (Cmarc)

History

  • 12-05-2008 — First submitted to CodeProject

Понравилась статья? Поделить с друзьями:
  • Как написать артикул товара на валберис
  • Как написать артериальное давление
  • Как написать артему шейнину на программу время покажет
  • Как написать артему шейнину лично
  • Как написать арпеджио