уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Пост восстановлен после переезда сайта на новый хостинг. Работа с zip в delphi появилась ещё в Delphi XE2. Однако это событие осталось практически незамеченным на фоне других нововведений, которыми пестрила Delphi XE2: разработка под MacOS и, соответственно, библиотеки Firemonkey, механизм LiveBinding и прочие «фичи», лежащие на поверхности. А про модуль delphi System.Zip особенно никто подробно и не рассказывал, разве, что здесь была небольшая заметка про zip в delphi. Сегодня я буду рассматривать работу с zip в delphi с позиции использования System.Zip в Delphi 10.3 Rio.

Введение

Для работы с ZIP-архивами в Delphi используется класс TZipFile. Этот класс предоставляет основные возможности для работы с zip, такие как чтение содержимого архива, запись архива, чтение заголовка архивного файла, упаковка и распаковка zip в delphi. И, несмотря на то, что TZipFile не содержит методов и свойств, непосредственно используемых для запароленных архивов, этот класс может предоставлять пользователю информацию о том, что файл зашифрован для того, чтобы использовать другие библиотеки delphi для распаковки зашифрованных zip-архивов.

Работу с модулем System.Zip и, в частности, с классом TZipFile, рассмотрим на примерах.

Чтение содержимого zip архива в Delphi

Для демонстрации возможностей чтения zip в delphi попробуем прочитать архив test.zip, содержащий три файла — текстовый и две схемы XSD для KML:

Для начала, прочитаем имена файлов, содержащихся в zip-архиве. Делается это так:

var Zip: TZipFile;
    ArchiveFile: String;
begin
  Zip:=TZipFile.Create;
  try
    Zip.Open('test.zip',zmRead);
    for ArchiveFile in Zip.FileNames do
      ShowMessage(ArchiveFile);
    Zip.Close;
  finally
    Zip.Free;
  end;
end;

Здесь мы создаем объект zip архива, затем открываем архив для чтения, используя метод Open и, используя свойство TZipFile.FileNames, получаем имена файлов в архиве. На что стоит обратить внимание:

  1. Путь к файлу в TZipFile.FileNames определяется относительно zip архива;
  2. В свойстве TZipFile.FileNames содержатся как имена файлов, так и папок архива.

Так, в приведенном выше примере программа выдаст следующим список:

  • kml/2.3.0/
  • kml/2.3.0/atom-author-link.xsd
  • kml/2.3.0/ogckml23.xsd
  • kml/2.3.0/ReadMe.txt

Метод Open имеет две версии:

procedure Open(const ZipFileName: string; OpenMode: TZipMode); overload;
procedure Open(ZipFileStream: TStream; OpenMode: TZipMode); overload;

В одной версии мы передаем в качестве zip архива имя файла, а во втором — поток TStream, содержащий zip архив. При этом, второй параметр — OpenMode может принимать следующие значения:

TZipMode = (zmClosed, 
            zmRead, //открываем zip архив для чтения 
            zmReadWrite, //открываем zip архив для чтения и добавления новых файлов 
            zmWrite);//создаем новый архив

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

property FileInfos: TArray read GetFileInfos;
property FileInfo[Index: Integer]: TZipHeader read GetFileInfo;

При этом тип TZipHeader имеет следующее описание:

  TZipHeader = packed record
    MadeByVersion:      UInt16; // Start of Central Header
    RequiredVersion:    UInt16; // Start of Local Header
    Flag:               UInt16;
    CompressionMethod:  UInt16;
    ModifiedDateTime:   UInt32;
    CRC32:              UInt32;
    CompressedSize:     UInt32;
    UncompressedSize:   UInt32;
    FileNameLength:     UInt16;
    ExtraFieldLength:   UInt16; // End of Local Header
    FileCommentLength:  UInt16;
    DiskNumberStart:    UInt16;
    InternalAttributes: UInt16;
    ExternalAttributes: UInt32;
    LocalHeaderOffset:  UInt32; // End of Central Header
    FileName: TBytes;
    ExtraField: TBytes;
    FileComment: TBytes;
    function GetUTF8Support: Boolean;
    procedure SetUTF8Support(value: Boolean);
    property UTF8Support: Boolean read GetUTF8Support write SetUTF8Support;
  end;

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

var
  Header: TZipHeader;
  Date: TDateTime;
begin
  Memo1.Lines.Clear;
  Header := Zip.FileInfos[ListBox1.ItemIndex];
  Memo1.Lines.Add('FileComment ' + StringOf(Header.FileComment));
  Memo1.Lines.Add('Method ' + TZipCompressionToString(TZipCompression(Header.CompressionMethod)));
  Memo1.Lines.Add('CompressedSize ' + Header.CompressedSize.ToString);
  Memo1.Lines.Add('UncompressedSize ' + Header.UncompressedSize.ToString);
  Memo1.Lines.Add('CRC32 ' + Header.CRC32.ToString);
  WinFileDateToDateTime(Header.ModifiedDateTime, Date);
  Memo1.Lines.Add('ModifiedDateTime ' + DateTimeToStr(Date));
end;

Здесь мы использовали два новых метода.

function TZipCompressionToString(Compression: TZipCompression): string;

Возвращает «человекопонятный» метод сжатия файла по значению, полученному из заголовка. TZipCompression — это перечислитель, содержащий все доступные методы сжатия файла.
Второй метод (WinFileDateToDateTime) переводит значение даты/времени из формата Windows в обычный TDateTime. Этот метод в System.Zip почему-то не публичный, в отличие от TZipCompressionToString, поэтому пришлось его «вытащить наружу», метод выглядит следующим образом:

function WinFileDateToDateTime(FileDate: UInt32; out DateTime: TDateTime): Boolean;
var
  LDate: TDateTime;
  LTime: TDateTime;
begin
    Result := TryEncodeDate(
    LongRec(FileDate).Hi shr 9 + 1980,
    LongRec(FileDate).Hi shr 5 and 15,
    LongRec(FileDate).Hi and 31,
    LDate);
 
  if Result then
  begin
    Result := TryEncodeTime(
      LongRec(FileDate).Lo shr 11,
      LongRec(FileDate).Lo shr 5 and 63,
      LongRec(FileDate).Lo and 31 shl 1, 0, LTime);
 
    if Result then
      DateTime := LDate + LTime;
  end;
end;

Распаковка файлов из ZIP в Delphi

Следующий момент — распаковка архива. Здесь TZipFile предоставляет возможность распаковки как отдельного файла, так и сразу всех файлов, содержащихся в архиве.

Для того, чтобы распаковать отдельный файл используется одна из версий метода:

procedure Extract(const FileName: string; const Path: string = ''; CreateSubdirs: Boolean = True); overload;
procedure Extract(Index: Integer; const Path: string = ''; CreateSubdirs: Boolean = True); overload;

Здесь:

  • FileName — файл, который необходимо распаковать (см. свойство TZipFile.FileNames)
  • Index — индекс файла в TZipFile.FileNames
  • Path — путь по которому необходимо разместить распакованный файл. По умолчанию файл размещается в директории рядом с exe-файлом
  • CreateSubdirs — указывает на то, следует ли воссоздать полный путь к файлу (True) или нет (False)

Для распаковки всего zip архива в delphi используется следующий метод:

procedure ExtractAll(const Path: string = '');

Если же вам необходимо содержимое какого-либо файла для дальнейшей работы в программе, то можно воспользоваться следующими методами:

procedure Read(const FileName: string; out Bytes: TBytes); overload;
procedure Read(Index: Integer; out Bytes: TBytes); overload;

Первый параметр в этих процедурах имеет тот же смысл, что и в методах Extract, а второй — массив байтов в который необходимо сохранить содержимое файла. Например, так можно вывести в Memo текст xsd-файла из нашего тестового архива:

var Bytes: TBytes;
begin
  Zip.Read(ListBox1.Items[ListBox1.ItemIndex],//файл из zip архива, выбранный в списке ListBox1
           Bytes);
  Memo1.Lines.Text:=StringOf(Bytes);
end;

Создание zip архива в Delphi

Для создания zip архива в delphi необходимо:

  1. Создать экземпляр TZipFile с режимом доступа zmReadWrite
  2. Добавить в архив файлы, используя метод TZipFile.Add
  3. Выполнить метод TZipFile.Close
  4. Уничтожить экземпляр TZipFile

Метод Add имеет следующее описание:

procedure Add(const FileName: string; const ArchiveFileName: string = ''; Compression: TZipCompression = zcDeflate); overload;
procedure Add(Data: TBytes; const ArchiveFileName: string; Compression: TZipCompression = zcDeflate); overload;
procedure Add(Data: TStream; const ArchiveFileName: string; Compression: TZipCompression = zcDeflate; AExternalAttributes: TFileAttributes = []); overload;
procedure Add(Data: TStream; LocalHeader: TZipHeader; CentralHeader: PZipHeader = nil); overload;

Здесь

  • FileName — имя внешнего файла, который необходимо добавить в архив
  • Data — содержимое внешнего файла, который необходимо добавить в архив
  • ArchiveFileName — имя файла в архиве
  • Compression — метод сжатия файла

Например, напишем консольное приложение, которое будет находить все файлы в папке archive рядом с exe-файлом и упаковывать их без всякой структуры в архив с названием test.zip:

program Archivator;
 
{$APPTYPE CONSOLE}
 
{$R *.res}
 
uses
  System.SysUtils,
  System.Zip,
  System.IOUtils;
 
const
  cZipName = 'test.zip';
  cZipFolder ='archive';
 
var ZipFile: TZipFile;
    zName: string;
    searchPath: string;
    ArchiveFiles: TArray;
    AFile: string;
begin
  //задаем имя zip архива
  zName:=TPath.Combine(ExtractFilePath(ParamStr(0)),
                      cZipName);
  //задаем путь к папке с файлами для упаковки
  searchPath:=TPath.Combine(ExtractFilePath(ParamStr(0)),
                      cZipFolder);
  ZipFile:=TZipFile.Create;
  try
    ZipFile.Open(zName,zmWrite);
    //ищем все файлы в директории
    ArchiveFiles:=TDirectory.GetFiles(searchPath);
    //добавляем все найденные файлы в архив
    for AFile in ArchiveFiles do
      begin
        Writeln('Добавляем в архив файл '+AFile);
        ZipFile.Add(AFile, ExtractFileName(AFile), zcDeflate);
      end;
    //закрываем файл
    ZipFile.Close;
  finally
    //уничтожаем TZipFile
    FreeAndNil(ZipFile);
  end;
  readln;
end.

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

Как работать с запароленными zip архивами в Delphi

Как я писал выше, к сожалению в TZipFile нет как таковых механизмов и методов работы с zip архивами с паролем. Однако, можно проверить наличие шифрования у архива и переадресовать работу с таким архивом другой библиотеке. Для того, чтобы определить, что файл зашифрован, необходимо определить обработчик события OnCreateDecompressStream или определить callback-функцию CreateDecompressStreamCallBack.

Например, определить, что zip архив защищен паролем можно так:

Назначаем обработчик OnCreateDecompressStream

var ZipFile: TZipFile;
begin
  ZipFile:=TZipFile.Create;
  ZipFile.OnCreateDecompressStream := OnCreateDecompressStream;
  ZipFile.Open('Password.zip',zmRead);
  try
    ZipFile.ExtractAll();
  finally
    ZipFile.Free;
  end;
end;

Сам обработчик выглядит следующим образом:

function TForm13.OnCreateDecompressStream(const InStream: TStream;
  const ZipFile: TZipFile; const Item: TZipHeader;
  IsEncrypted: Boolean): TStream;
begin
  try
    if IsEncrypted then
    begin
      ShowMessage('Зашифровано');
    end
    else
      Result := InStream;
  except
    on E: Exception do
    begin
      Result := InStream;
    end;
  end;
end;

В принципе, этот подход был показан давным давно в официальной документации Embarcadero. Однако, при тестировании TZipFile обнаружилось, что, если файл зашифрован одним из методов AES (AES128, AES192, AES256), то вызывается исключение при создании потока TZDecompressionStream и, следовательно, до вызова события OnCreateDecompressStream дело не доходит. Методом AES позволяет шифровать файлы, например, тот же Total Commander:

Однако, если выбрать способ ZIP 2.0, то все работает как надо.

Отображение прогресса создания или распаковки zip архива в Delphi

При добавлении в zip архив большого файла или, наоборот, распаковки большого zip архива в delphi хотелось бы, чтобы наша программа не «висла». TZipFile предоставляет такую возможность с помощью события OnProgress:

TZipProgressEvent = procedure(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64) of object;

Здесь

  • FileName — имя файла, который в данный момент обрабатывается
  • Header — информация о файле
  • Position — количество обработанный байт файла

Например, чтобы получить текущий прогресс упаковки файла в zip архив в delphi можно использовать простую формулу:

Position / Header.UnCompressedSize)*100

Ну и, конечно, если вы работаете с TZipFile не в отдельном потоке, то стоит в обработчике также указать:

Application.ProcessMessages

Вот, в принципе и вся основная информация по работе delphi с zip архивами «из коробки». Остается только добавить, что помимо рассмотренных выше методов и свойств у TZipFile также имеются классовые методы, позволяющие работать с zip архивами без создания экземпляра TZipFile.

5 6 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
1 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
wolfstar
wolfstar
08/08/2021 22:36

Интересная тема, но для новичков розобратся будет трудновато. К тому же автор наверное забросил сайт что очень печально.